blob: a26f9e4097a28a918060d73ba8309d1dd57c071e [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 Hongcfb917a2020-05-07 14:58:20 -0700113 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800114
Tao Bao08c190f2019-06-03 23:07:58 -0700115# Chained VBMeta partitions.
116AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
117
Tianjie Xu861f4132018-09-12 11:49:33 -0700118# Partitions that should have their care_map added to META/care_map.pb
Yifan Hongcfb917a2020-05-07 14:58:20 -0700119PARTITIONS_WITH_CARE_MAP = (
120 'system',
121 'vendor',
122 'product',
123 'system_ext',
124 'odm',
125 'vendor_dlkm',
126)
Tianjie Xu861f4132018-09-12 11:49:33 -0700127
128
Tianjie Xu209db462016-05-24 17:34:52 -0700129class ErrorCode(object):
130 """Define error_codes for failures that happen during the actual
131 update package installation.
132
133 Error codes 0-999 are reserved for failures before the package
134 installation (i.e. low battery, package verification failure).
135 Detailed code in 'bootable/recovery/error_code.h' """
136
137 SYSTEM_VERIFICATION_FAILURE = 1000
138 SYSTEM_UPDATE_FAILURE = 1001
139 SYSTEM_UNEXPECTED_CONTENTS = 1002
140 SYSTEM_NONZERO_CONTENTS = 1003
141 SYSTEM_RECOVER_FAILURE = 1004
142 VENDOR_VERIFICATION_FAILURE = 2000
143 VENDOR_UPDATE_FAILURE = 2001
144 VENDOR_UNEXPECTED_CONTENTS = 2002
145 VENDOR_NONZERO_CONTENTS = 2003
146 VENDOR_RECOVER_FAILURE = 2004
147 OEM_PROP_MISMATCH = 3000
148 FINGERPRINT_MISMATCH = 3001
149 THUMBPRINT_MISMATCH = 3002
150 OLDER_BUILD = 3003
151 DEVICE_MISMATCH = 3004
152 BAD_PATCH_FILE = 3005
153 INSUFFICIENT_CACHE_SPACE = 3006
154 TUNE_PARTITION_FAILURE = 3007
155 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800156
Tao Bao80921982018-03-21 21:02:19 -0700157
Dan Albert8b72aef2015-03-23 19:13:21 -0700158class ExternalError(RuntimeError):
159 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700160
161
Tao Bao32fcdab2018-10-12 10:30:39 -0700162def InitLogging():
163 DEFAULT_LOGGING_CONFIG = {
164 'version': 1,
165 'disable_existing_loggers': False,
166 'formatters': {
167 'standard': {
168 'format':
169 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
170 'datefmt': '%Y-%m-%d %H:%M:%S',
171 },
172 },
173 'handlers': {
174 'default': {
175 'class': 'logging.StreamHandler',
176 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700177 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700178 },
179 },
180 'loggers': {
181 '': {
182 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700183 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700184 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700185 }
186 }
187 }
188 env_config = os.getenv('LOGGING_CONFIG')
189 if env_config:
190 with open(env_config) as f:
191 config = json.load(f)
192 else:
193 config = DEFAULT_LOGGING_CONFIG
194
195 # Increase the logging level for verbose mode.
196 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700197 config = copy.deepcopy(config)
198 config['handlers']['default']['level'] = 'INFO'
199
200 if OPTIONS.logfile:
201 config = copy.deepcopy(config)
202 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400203 'class': 'logging.FileHandler',
204 'formatter': 'standard',
205 'level': 'INFO',
206 'mode': 'w',
207 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700208 }
209 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700210
211 logging.config.dictConfig(config)
212
213
Tao Bao39451582017-05-04 11:10:47 -0700214def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700215 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700216
Tao Bao73dd4f42018-10-04 16:25:33 -0700217 Args:
218 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700219 verbose: Whether the commands should be shown. Default to the global
220 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700221 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
222 stdin, etc. stdout and stderr will default to subprocess.PIPE and
223 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800224 universal_newlines will default to True, as most of the users in
225 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700226
227 Returns:
228 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700229 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700230 if 'stdout' not in kwargs and 'stderr' not in kwargs:
231 kwargs['stdout'] = subprocess.PIPE
232 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800233 if 'universal_newlines' not in kwargs:
234 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700235 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400236 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700237 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700238 return subprocess.Popen(args, **kwargs)
239
240
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800241def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800242 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800243
244 Args:
245 args: The command represented as a list of strings.
246 verbose: Whether the commands should be shown. Default to the global
247 verbosity if unspecified.
248 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
249 stdin, etc. stdout and stderr will default to subprocess.PIPE and
250 subprocess.STDOUT respectively unless caller specifies any of them.
251
Bill Peckham889b0c62019-02-21 18:53:37 -0800252 Raises:
253 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800254 """
255 proc = Run(args, verbose=verbose, **kwargs)
256 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800257
258 if proc.returncode != 0:
259 raise ExternalError(
260 "Failed to run command '{}' (exit code {})".format(
261 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800262
263
Tao Bao986ee862018-10-04 15:46:16 -0700264def RunAndCheckOutput(args, verbose=None, **kwargs):
265 """Runs the given command and returns the output.
266
267 Args:
268 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700269 verbose: Whether the commands should be shown. Default to the global
270 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700271 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
272 stdin, etc. stdout and stderr will default to subprocess.PIPE and
273 subprocess.STDOUT respectively unless caller specifies any of them.
274
275 Returns:
276 The output string.
277
278 Raises:
279 ExternalError: On non-zero exit from the command.
280 """
Tao Bao986ee862018-10-04 15:46:16 -0700281 proc = Run(args, verbose=verbose, **kwargs)
282 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800283 if output is None:
284 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700285 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400286 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700287 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700288 if proc.returncode != 0:
289 raise ExternalError(
290 "Failed to run command '{}' (exit code {}):\n{}".format(
291 args, proc.returncode, output))
292 return output
293
294
Tao Baoc765cca2018-01-31 17:32:40 -0800295def RoundUpTo4K(value):
296 rounded_up = value + 4095
297 return rounded_up - (rounded_up % 4096)
298
299
Ying Wang7e6d4e42010-12-13 16:25:36 -0800300def CloseInheritedPipes():
301 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
302 before doing other work."""
303 if platform.system() != "Darwin":
304 return
305 for d in range(3, 1025):
306 try:
307 stat = os.fstat(d)
308 if stat is not None:
309 pipebit = stat[0] & 0x1000
310 if pipebit != 0:
311 os.close(d)
312 except OSError:
313 pass
314
315
Tao Bao1c320f82019-10-04 23:25:12 -0700316class BuildInfo(object):
317 """A class that holds the information for a given build.
318
319 This class wraps up the property querying for a given source or target build.
320 It abstracts away the logic of handling OEM-specific properties, and caches
321 the commonly used properties such as fingerprint.
322
323 There are two types of info dicts: a) build-time info dict, which is generated
324 at build time (i.e. included in a target_files zip); b) OEM info dict that is
325 specified at package generation time (via command line argument
326 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
327 having "oem_fingerprint_properties" in build-time info dict), all the queries
328 would be answered based on build-time info dict only. Otherwise if using
329 OEM-specific properties, some of them will be calculated from two info dicts.
330
331 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800332 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700333
334 Attributes:
335 info_dict: The build-time info dict.
336 is_ab: Whether it's a build that uses A/B OTA.
337 oem_dicts: A list of OEM dicts.
338 oem_props: A list of OEM properties that should be read from OEM dicts; None
339 if the build doesn't use any OEM-specific property.
340 fingerprint: The fingerprint of the build, which would be calculated based
341 on OEM properties if applicable.
342 device: The device name, which could come from OEM dicts if applicable.
343 """
344
345 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
346 "ro.product.manufacturer", "ro.product.model",
347 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700348 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
349 "product", "odm", "vendor", "system_ext", "system"]
350 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
351 "product", "product_services", "odm", "vendor", "system"]
352 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700353
Tao Bao3ed35d32019-10-07 20:48:48 -0700354 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700355 """Initializes a BuildInfo instance with the given dicts.
356
357 Note that it only wraps up the given dicts, without making copies.
358
359 Arguments:
360 info_dict: The build-time info dict.
361 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
362 that it always uses the first dict to calculate the fingerprint or the
363 device name. The rest would be used for asserting OEM properties only
364 (e.g. one package can be installed on one of these devices).
365
366 Raises:
367 ValueError: On invalid inputs.
368 """
369 self.info_dict = info_dict
370 self.oem_dicts = oem_dicts
371
372 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700373
Hongguang Chend7c160f2020-05-03 21:24:26 -0700374 # Skip _oem_props if oem_dicts is None to use BuildInfo in
375 # sign_target_files_apks
376 if self.oem_dicts:
377 self._oem_props = info_dict.get("oem_fingerprint_properties")
378 else:
379 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700380
Daniel Normand5fe8622020-01-08 17:01:11 -0800381 def check_fingerprint(fingerprint):
382 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
383 raise ValueError(
384 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
385 "3.2.2. Build Parameters.".format(fingerprint))
386
Daniel Normand5fe8622020-01-08 17:01:11 -0800387 self._partition_fingerprints = {}
388 for partition in PARTITIONS_WITH_CARE_MAP:
389 try:
390 fingerprint = self.CalculatePartitionFingerprint(partition)
391 check_fingerprint(fingerprint)
392 self._partition_fingerprints[partition] = fingerprint
393 except ExternalError:
394 continue
395 if "system" in self._partition_fingerprints:
396 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
397 # need a fingerprint when creating the image.
398 self._partition_fingerprints[
399 "system_other"] = self._partition_fingerprints["system"]
400
Tao Bao1c320f82019-10-04 23:25:12 -0700401 # These two should be computed only after setting self._oem_props.
402 self._device = self.GetOemProperty("ro.product.device")
403 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800404 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700405
406 @property
407 def is_ab(self):
408 return self._is_ab
409
410 @property
411 def device(self):
412 return self._device
413
414 @property
415 def fingerprint(self):
416 return self._fingerprint
417
418 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700419 def oem_props(self):
420 return self._oem_props
421
422 def __getitem__(self, key):
423 return self.info_dict[key]
424
425 def __setitem__(self, key, value):
426 self.info_dict[key] = value
427
428 def get(self, key, default=None):
429 return self.info_dict.get(key, default)
430
431 def items(self):
432 return self.info_dict.items()
433
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000434 def _GetRawBuildProp(self, prop, partition):
435 prop_file = '{}.build.prop'.format(
436 partition) if partition else 'build.prop'
437 partition_props = self.info_dict.get(prop_file)
438 if not partition_props:
439 return None
440 return partition_props.GetProp(prop)
441
Daniel Normand5fe8622020-01-08 17:01:11 -0800442 def GetPartitionBuildProp(self, prop, partition):
443 """Returns the inquired build property for the provided partition."""
444 # If provided a partition for this property, only look within that
445 # partition's build.prop.
446 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
447 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
448 else:
449 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000450
451 prop_val = self._GetRawBuildProp(prop, partition)
452 if prop_val is not None:
453 return prop_val
454 raise ExternalError("couldn't find %s in %s.build.prop" %
455 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800456
Tao Bao1c320f82019-10-04 23:25:12 -0700457 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800458 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700459 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
460 return self._ResolveRoProductBuildProp(prop)
461
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000462 prop_val = self._GetRawBuildProp(prop, None)
463 if prop_val is not None:
464 return prop_val
465
466 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700467
468 def _ResolveRoProductBuildProp(self, prop):
469 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000470 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700471 if prop_val:
472 return prop_val
473
Steven Laver8e2086e2020-04-27 16:26:31 -0700474 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000475 source_order_val = self._GetRawBuildProp(
476 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700477 if source_order_val:
478 source_order = source_order_val.split(",")
479 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700480 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700481
482 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700483 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700484 raise ExternalError(
485 "Invalid ro.product.property_source_order '{}'".format(source_order))
486
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000487 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700488 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000489 "ro.product", "ro.product.{}".format(source_partition), 1)
490 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700491 if prop_val:
492 return prop_val
493
494 raise ExternalError("couldn't resolve {}".format(prop))
495
Steven Laver8e2086e2020-04-27 16:26:31 -0700496 def _GetRoProductPropsDefaultSourceOrder(self):
497 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
498 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000499 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700500 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000501 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700502 if android_version == "10":
503 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
504 # NOTE: float() conversion of android_version will have rounding error.
505 # We are checking for "9" or less, and using "< 10" is well outside of
506 # possible floating point rounding.
507 try:
508 android_version_val = float(android_version)
509 except ValueError:
510 android_version_val = 0
511 if android_version_val < 10:
512 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
513 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
514
Tao Bao1c320f82019-10-04 23:25:12 -0700515 def GetOemProperty(self, key):
516 if self.oem_props is not None and key in self.oem_props:
517 return self.oem_dicts[0][key]
518 return self.GetBuildProp(key)
519
Daniel Normand5fe8622020-01-08 17:01:11 -0800520 def GetPartitionFingerprint(self, partition):
521 return self._partition_fingerprints.get(partition, None)
522
523 def CalculatePartitionFingerprint(self, partition):
524 try:
525 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
526 except ExternalError:
527 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
528 self.GetPartitionBuildProp("ro.product.brand", partition),
529 self.GetPartitionBuildProp("ro.product.name", partition),
530 self.GetPartitionBuildProp("ro.product.device", partition),
531 self.GetPartitionBuildProp("ro.build.version.release", partition),
532 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400533 self.GetPartitionBuildProp(
534 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800535 self.GetPartitionBuildProp("ro.build.type", partition),
536 self.GetPartitionBuildProp("ro.build.tags", partition))
537
Tao Bao1c320f82019-10-04 23:25:12 -0700538 def CalculateFingerprint(self):
539 if self.oem_props is None:
540 try:
541 return self.GetBuildProp("ro.build.fingerprint")
542 except ExternalError:
543 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
544 self.GetBuildProp("ro.product.brand"),
545 self.GetBuildProp("ro.product.name"),
546 self.GetBuildProp("ro.product.device"),
547 self.GetBuildProp("ro.build.version.release"),
548 self.GetBuildProp("ro.build.id"),
549 self.GetBuildProp("ro.build.version.incremental"),
550 self.GetBuildProp("ro.build.type"),
551 self.GetBuildProp("ro.build.tags"))
552 return "%s/%s/%s:%s" % (
553 self.GetOemProperty("ro.product.brand"),
554 self.GetOemProperty("ro.product.name"),
555 self.GetOemProperty("ro.product.device"),
556 self.GetBuildProp("ro.build.thumbprint"))
557
558 def WriteMountOemScript(self, script):
559 assert self.oem_props is not None
560 recovery_mount_options = self.info_dict.get("recovery_mount_options")
561 script.Mount("/oem", recovery_mount_options)
562
563 def WriteDeviceAssertions(self, script, oem_no_mount):
564 # Read the property directly if not using OEM properties.
565 if not self.oem_props:
566 script.AssertDevice(self.device)
567 return
568
569 # Otherwise assert OEM properties.
570 if not self.oem_dicts:
571 raise ExternalError(
572 "No OEM file provided to answer expected assertions")
573
574 for prop in self.oem_props.split():
575 values = []
576 for oem_dict in self.oem_dicts:
577 if prop in oem_dict:
578 values.append(oem_dict[prop])
579 if not values:
580 raise ExternalError(
581 "The OEM file is missing the property %s" % (prop,))
582 script.AssertOemProperty(prop, values, oem_no_mount)
583
584
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000585def ReadFromInputFile(input_file, fn):
586 """Reads the contents of fn from input zipfile or directory."""
587 if isinstance(input_file, zipfile.ZipFile):
588 return input_file.read(fn).decode()
589 else:
590 path = os.path.join(input_file, *fn.split("/"))
591 try:
592 with open(path) as f:
593 return f.read()
594 except IOError as e:
595 if e.errno == errno.ENOENT:
596 raise KeyError(fn)
597
598
Tao Bao410ad8b2018-08-24 12:08:38 -0700599def LoadInfoDict(input_file, repacking=False):
600 """Loads the key/value pairs from the given input target_files.
601
602 It reads `META/misc_info.txt` file in the target_files input, does sanity
603 checks and returns the parsed key/value pairs for to the given build. It's
604 usually called early when working on input target_files files, e.g. when
605 generating OTAs, or signing builds. Note that the function may be called
606 against an old target_files file (i.e. from past dessert releases). So the
607 property parsing needs to be backward compatible.
608
609 In a `META/misc_info.txt`, a few properties are stored as links to the files
610 in the PRODUCT_OUT directory. It works fine with the build system. However,
611 they are no longer available when (re)generating images from target_files zip.
612 When `repacking` is True, redirect these properties to the actual files in the
613 unzipped directory.
614
615 Args:
616 input_file: The input target_files file, which could be an open
617 zipfile.ZipFile instance, or a str for the dir that contains the files
618 unzipped from a target_files file.
619 repacking: Whether it's trying repack an target_files file after loading the
620 info dict (default: False). If so, it will rewrite a few loaded
621 properties (e.g. selinux_fc, root_dir) to point to the actual files in
622 target_files file. When doing repacking, `input_file` must be a dir.
623
624 Returns:
625 A dict that contains the parsed key/value pairs.
626
627 Raises:
628 AssertionError: On invalid input arguments.
629 ValueError: On malformed input values.
630 """
631 if repacking:
632 assert isinstance(input_file, str), \
633 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700634
Doug Zongkerc9253822014-02-04 12:17:58 -0800635 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000636 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800637
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700638 try:
Michael Runge6e836112014-04-15 17:40:21 -0700639 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700640 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700641 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700642
Tao Bao410ad8b2018-08-24 12:08:38 -0700643 if "recovery_api_version" not in d:
644 raise ValueError("Failed to find 'recovery_api_version'")
645 if "fstab_version" not in d:
646 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800647
Tao Bao410ad8b2018-08-24 12:08:38 -0700648 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700649 # "selinux_fc" properties should point to the file_contexts files
650 # (file_contexts.bin) under META/.
651 for key in d:
652 if key.endswith("selinux_fc"):
653 fc_basename = os.path.basename(d[key])
654 fc_config = os.path.join(input_file, "META", fc_basename)
655 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700656
Daniel Norman72c626f2019-05-13 15:58:14 -0700657 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700658
Tom Cherryd14b8952018-08-09 14:26:00 -0700659 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700660 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700661 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700662 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700663
David Anderson0ec64ac2019-12-06 12:21:18 -0800664 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700665 for part_name in ["system", "vendor", "system_ext", "product", "odm",
666 "vendor_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800667 key_name = part_name + "_base_fs_file"
668 if key_name not in d:
669 continue
670 basename = os.path.basename(d[key_name])
671 base_fs_file = os.path.join(input_file, "META", basename)
672 if os.path.exists(base_fs_file):
673 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700674 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700675 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800676 "Failed to find %s base fs file: %s", part_name, base_fs_file)
677 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700678
Doug Zongker37974732010-09-16 17:44:38 -0700679 def makeint(key):
680 if key in d:
681 d[key] = int(d[key], 0)
682
683 makeint("recovery_api_version")
684 makeint("blocksize")
685 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700686 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700687 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700688 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700689 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800690 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700691
Steve Muckle903a1ca2020-05-07 17:32:10 -0700692 boot_images = "boot.img"
693 if "boot_images" in d:
694 boot_images = d["boot_images"]
695 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400696 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700697
Tao Bao765668f2019-10-04 22:03:00 -0700698 # Load recovery fstab if applicable.
699 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800700
Tianjie Xu861f4132018-09-12 11:49:33 -0700701 # Tries to load the build props for all partitions with care_map, including
702 # system and vendor.
703 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800704 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000705 d[partition_prop] = PartitionBuildProps.FromInputFile(
706 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700707 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800708
Tao Bao3ed35d32019-10-07 20:48:48 -0700709 # Set up the salt (based on fingerprint) that will be used when adding AVB
710 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800711 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700712 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800713 for partition in PARTITIONS_WITH_CARE_MAP:
714 fingerprint = build_info.GetPartitionFingerprint(partition)
715 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400716 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800717
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700718 return d
719
Tao Baod1de6f32017-03-01 16:38:48 -0800720
Daniel Norman4cc9df62019-07-18 10:11:07 -0700721def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900722 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700723 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900724
Daniel Norman4cc9df62019-07-18 10:11:07 -0700725
726def LoadDictionaryFromFile(file_path):
727 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900728 return LoadDictionaryFromLines(lines)
729
730
Michael Runge6e836112014-04-15 17:40:21 -0700731def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700732 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700733 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700734 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700735 if not line or line.startswith("#"):
736 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700737 if "=" in line:
738 name, value = line.split("=", 1)
739 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700740 return d
741
Tao Baod1de6f32017-03-01 16:38:48 -0800742
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000743class PartitionBuildProps(object):
744 """The class holds the build prop of a particular partition.
745
746 This class loads the build.prop and holds the build properties for a given
747 partition. It also partially recognizes the 'import' statement in the
748 build.prop; and calculates alternative values of some specific build
749 properties during runtime.
750
751 Attributes:
752 input_file: a zipped target-file or an unzipped target-file directory.
753 partition: name of the partition.
754 props_allow_override: a list of build properties to search for the
755 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000756 build_props: a dict of build properties for the given partition.
757 prop_overrides: a set of props that are overridden by import.
758 placeholder_values: A dict of runtime variables' values to replace the
759 placeholders in the build.prop file. We expect exactly one value for
760 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000761 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400762
Tianjie Xu9afb2212020-05-10 21:48:15 +0000763 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000764 self.input_file = input_file
765 self.partition = name
766 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000767 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000768 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000769 self.prop_overrides = set()
770 self.placeholder_values = {}
771 if placeholder_values:
772 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000773
774 @staticmethod
775 def FromDictionary(name, build_props):
776 """Constructs an instance from a build prop dictionary."""
777
778 props = PartitionBuildProps("unknown", name)
779 props.build_props = build_props.copy()
780 return props
781
782 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000783 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000784 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000785 data = ''
786 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
787 '{}/build.prop'.format(name.upper())]:
788 try:
789 data = ReadFromInputFile(input_file, prop_file)
790 break
791 except KeyError:
792 logger.warning('Failed to read %s', prop_file)
793
Tianjie Xu9afb2212020-05-10 21:48:15 +0000794 props = PartitionBuildProps(input_file, name, placeholder_values)
795 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000796 return props
797
Tianjie Xu9afb2212020-05-10 21:48:15 +0000798 def _LoadBuildProp(self, data):
799 for line in data.split('\n'):
800 line = line.strip()
801 if not line or line.startswith("#"):
802 continue
803 if line.startswith("import"):
804 overrides = self._ImportParser(line)
805 duplicates = self.prop_overrides.intersection(overrides.keys())
806 if duplicates:
807 raise ValueError('prop {} is overridden multiple times'.format(
808 ','.join(duplicates)))
809 self.prop_overrides = self.prop_overrides.union(overrides.keys())
810 self.build_props.update(overrides)
811 elif "=" in line:
812 name, value = line.split("=", 1)
813 if name in self.prop_overrides:
814 raise ValueError('prop {} is set again after overridden by import '
815 'statement'.format(name))
816 self.build_props[name] = value
817
818 def _ImportParser(self, line):
819 """Parses the build prop in a given import statement."""
820
821 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400822 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000823 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700824
825 if len(tokens) == 3:
826 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
827 return {}
828
Tianjie Xu9afb2212020-05-10 21:48:15 +0000829 import_path = tokens[1]
830 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
831 raise ValueError('Unrecognized import path {}'.format(line))
832
833 # We only recognize a subset of import statement that the init process
834 # supports. And we can loose the restriction based on how the dynamic
835 # fingerprint is used in practice. The placeholder format should be
836 # ${placeholder}, and its value should be provided by the caller through
837 # the placeholder_values.
838 for prop, value in self.placeholder_values.items():
839 prop_place_holder = '${{{}}}'.format(prop)
840 if prop_place_holder in import_path:
841 import_path = import_path.replace(prop_place_holder, value)
842 if '$' in import_path:
843 logger.info('Unresolved place holder in import path %s', import_path)
844 return {}
845
846 import_path = import_path.replace('/{}'.format(self.partition),
847 self.partition.upper())
848 logger.info('Parsing build props override from %s', import_path)
849
850 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
851 d = LoadDictionaryFromLines(lines)
852 return {key: val for key, val in d.items()
853 if key in self.props_allow_override}
854
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000855 def GetProp(self, prop):
856 return self.build_props.get(prop)
857
858
Tianjie Xucfa86222016-03-07 16:31:19 -0800859def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
860 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700861 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700862 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700863 self.mount_point = mount_point
864 self.fs_type = fs_type
865 self.device = device
866 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700867 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700868 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700869
870 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800871 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700872 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700873 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700874 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700875
Tao Baod1de6f32017-03-01 16:38:48 -0800876 assert fstab_version == 2
877
878 d = {}
879 for line in data.split("\n"):
880 line = line.strip()
881 if not line or line.startswith("#"):
882 continue
883
884 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
885 pieces = line.split()
886 if len(pieces) != 5:
887 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
888
889 # Ignore entries that are managed by vold.
890 options = pieces[4]
891 if "voldmanaged=" in options:
892 continue
893
894 # It's a good line, parse it.
895 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700896 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800897 options = options.split(",")
898 for i in options:
899 if i.startswith("length="):
900 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700901 elif i == "slotselect":
902 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800903 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800904 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700905 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800906
Tao Baod1de6f32017-03-01 16:38:48 -0800907 mount_flags = pieces[3]
908 # Honor the SELinux context if present.
909 context = None
910 for i in mount_flags.split(","):
911 if i.startswith("context="):
912 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800913
Tao Baod1de6f32017-03-01 16:38:48 -0800914 mount_point = pieces[1]
915 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700916 device=pieces[0], length=length, context=context,
917 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800918
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700919 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700920 # system. Other areas assume system is always at "/system" so point /system
921 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700922 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800923 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700924 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700925 return d
926
927
Tao Bao765668f2019-10-04 22:03:00 -0700928def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
929 """Finds the path to recovery fstab and loads its contents."""
930 # recovery fstab is only meaningful when installing an update via recovery
931 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -0700932 if info_dict.get('ab_update') == 'true' and \
933 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -0700934 return None
935
936 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
937 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
938 # cases, since it may load the info_dict from an old build (e.g. when
939 # generating incremental OTAs from that build).
940 system_root_image = info_dict.get('system_root_image') == 'true'
941 if info_dict.get('no_recovery') != 'true':
942 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
943 if isinstance(input_file, zipfile.ZipFile):
944 if recovery_fstab_path not in input_file.namelist():
945 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
946 else:
947 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
948 if not os.path.exists(path):
949 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
950 return LoadRecoveryFSTab(
951 read_helper, info_dict['fstab_version'], recovery_fstab_path,
952 system_root_image)
953
954 if info_dict.get('recovery_as_boot') == 'true':
955 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
956 if isinstance(input_file, zipfile.ZipFile):
957 if recovery_fstab_path not in input_file.namelist():
958 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
959 else:
960 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
961 if not os.path.exists(path):
962 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
963 return LoadRecoveryFSTab(
964 read_helper, info_dict['fstab_version'], recovery_fstab_path,
965 system_root_image)
966
967 return None
968
969
Doug Zongker37974732010-09-16 17:44:38 -0700970def DumpInfoDict(d):
971 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700972 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700973
Dan Albert8b72aef2015-03-23 19:13:21 -0700974
Daniel Norman55417142019-11-25 16:04:36 -0800975def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700976 """Merges dynamic partition info variables.
977
978 Args:
979 framework_dict: The dictionary of dynamic partition info variables from the
980 partial framework target files.
981 vendor_dict: The dictionary of dynamic partition info variables from the
982 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700983
984 Returns:
985 The merged dynamic partition info dictionary.
986 """
987 merged_dict = {}
988 # Partition groups and group sizes are defined by the vendor dict because
989 # these values may vary for each board that uses a shared system image.
990 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800991 framework_dynamic_partition_list = framework_dict.get(
992 "dynamic_partition_list", "")
993 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
994 merged_dict["dynamic_partition_list"] = ("%s %s" % (
995 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700996 for partition_group in merged_dict["super_partition_groups"].split(" "):
997 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800998 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700999 if key not in vendor_dict:
1000 raise ValueError("Vendor dict does not contain required key %s." % key)
1001 merged_dict[key] = vendor_dict[key]
1002
1003 # Set the partition group's partition list using a concatenation of the
1004 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001005 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001006 merged_dict[key] = (
1007 "%s %s" %
1008 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301009
1010 # Pick virtual ab related flags from vendor dict, if defined.
1011 if "virtual_ab" in vendor_dict.keys():
Kelvin Zhang0876c412020-06-23 15:06:58 -04001012 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301013 if "virtual_ab_retrofit" in vendor_dict.keys():
Kelvin Zhang0876c412020-06-23 15:06:58 -04001014 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001015 return merged_dict
1016
1017
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001018def AppendAVBSigningArgs(cmd, partition):
1019 """Append signing arguments for avbtool."""
1020 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1021 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001022 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1023 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1024 if os.path.exists(new_key_path):
1025 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001026 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1027 if key_path and algorithm:
1028 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001029 avb_salt = OPTIONS.info_dict.get("avb_salt")
1030 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001031 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001032 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001033
1034
Tao Bao765668f2019-10-04 22:03:00 -07001035def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001036 """Returns the VBMeta arguments for partition.
1037
1038 It sets up the VBMeta argument by including the partition descriptor from the
1039 given 'image', or by configuring the partition as a chained partition.
1040
1041 Args:
1042 partition: The name of the partition (e.g. "system").
1043 image: The path to the partition image.
1044 info_dict: A dict returned by common.LoadInfoDict(). Will use
1045 OPTIONS.info_dict if None has been given.
1046
1047 Returns:
1048 A list of VBMeta arguments.
1049 """
1050 if info_dict is None:
1051 info_dict = OPTIONS.info_dict
1052
1053 # Check if chain partition is used.
1054 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001055 if not key_path:
1056 return ["--include_descriptors_from_image", image]
1057
1058 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1059 # into vbmeta.img. The recovery image will be configured on an independent
1060 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1061 # See details at
1062 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001063 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001064 return []
1065
1066 # Otherwise chain the partition into vbmeta.
1067 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1068 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001069
1070
Tao Bao02a08592018-07-22 12:40:45 -07001071def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1072 """Constructs and returns the arg to build or verify a chained partition.
1073
1074 Args:
1075 partition: The partition name.
1076 info_dict: The info dict to look up the key info and rollback index
1077 location.
1078 key: The key to be used for building or verifying the partition. Defaults to
1079 the key listed in info_dict.
1080
1081 Returns:
1082 A string of form "partition:rollback_index_location:key" that can be used to
1083 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001084 """
1085 if key is None:
1086 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001087 if key and not os.path.exists(key) and OPTIONS.search_path:
1088 new_key_path = os.path.join(OPTIONS.search_path, key)
1089 if os.path.exists(new_key_path):
1090 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001091 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001092 rollback_index_location = info_dict[
1093 "avb_" + partition + "_rollback_index_location"]
1094 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1095
1096
Tianjie20dd8f22020-04-19 15:51:16 -07001097def ConstructAftlMakeImageCommands(output_image):
1098 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001099
1100 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001101 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001102 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1103 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1104 'No AFTL manufacturer key provided.'
1105
1106 vbmeta_image = MakeTempFile()
1107 os.rename(output_image, vbmeta_image)
1108 build_info = BuildInfo(OPTIONS.info_dict)
1109 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001110 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001111 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001112 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001113 "--vbmeta_image_path", vbmeta_image,
1114 "--output", output_image,
1115 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001116 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001117 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1118 "--algorithm", "SHA256_RSA4096",
1119 "--padding", "4096"]
1120 if OPTIONS.aftl_signer_helper:
1121 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001122 return aftl_cmd
1123
1124
1125def AddAftlInclusionProof(output_image):
1126 """Appends the aftl inclusion proof to the vbmeta image."""
1127
1128 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001129 RunAndCheckOutput(aftl_cmd)
1130
1131 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1132 output_image, '--transparency_log_pub_keys',
1133 OPTIONS.aftl_key_path]
1134 RunAndCheckOutput(verify_cmd)
1135
1136
Daniel Norman276f0622019-07-26 14:13:51 -07001137def BuildVBMeta(image_path, partitions, name, needed_partitions):
1138 """Creates a VBMeta image.
1139
1140 It generates the requested VBMeta image. The requested image could be for
1141 top-level or chained VBMeta image, which is determined based on the name.
1142
1143 Args:
1144 image_path: The output path for the new VBMeta image.
1145 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001146 values. Only valid partition names are accepted, as partitions listed
1147 in common.AVB_PARTITIONS and custom partitions listed in
1148 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001149 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1150 needed_partitions: Partitions whose descriptors should be included into the
1151 generated VBMeta image.
1152
1153 Raises:
1154 AssertionError: On invalid input args.
1155 """
1156 avbtool = OPTIONS.info_dict["avb_avbtool"]
1157 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1158 AppendAVBSigningArgs(cmd, name)
1159
Hongguang Chenf23364d2020-04-27 18:36:36 -07001160 custom_partitions = OPTIONS.info_dict.get(
1161 "avb_custom_images_partition_list", "").strip().split()
1162
Daniel Norman276f0622019-07-26 14:13:51 -07001163 for partition, path in partitions.items():
1164 if partition not in needed_partitions:
1165 continue
1166 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001167 partition in AVB_VBMETA_PARTITIONS or
1168 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001169 'Unknown partition: {}'.format(partition)
1170 assert os.path.exists(path), \
1171 'Failed to find {} for {}'.format(path, partition)
1172 cmd.extend(GetAvbPartitionArg(partition, path))
1173
1174 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1175 if args and args.strip():
1176 split_args = shlex.split(args)
1177 for index, arg in enumerate(split_args[:-1]):
1178 # Sanity check that the image file exists. Some images might be defined
1179 # as a path relative to source tree, which may not be available at the
1180 # same location when running this script (we have the input target_files
1181 # zip only). For such cases, we additionally scan other locations (e.g.
1182 # IMAGES/, RADIO/, etc) before bailing out.
1183 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001184 chained_image = split_args[index + 1]
1185 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001186 continue
1187 found = False
1188 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1189 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001190 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001191 if os.path.exists(alt_path):
1192 split_args[index + 1] = alt_path
1193 found = True
1194 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001195 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001196 cmd.extend(split_args)
1197
1198 RunAndCheckOutput(cmd)
1199
Tianjie Xueaed60c2020-03-12 00:33:28 -07001200 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001201 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001202 AddAftlInclusionProof(image_path)
1203
Daniel Norman276f0622019-07-26 14:13:51 -07001204
J. Avila98cd4cc2020-06-10 20:09:10 +00001205def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001206 ramdisk_img = tempfile.NamedTemporaryFile()
1207
1208 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1209 cmd = ["mkbootfs", "-f", fs_config_file,
1210 os.path.join(sourcedir, "RAMDISK")]
1211 else:
1212 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1213 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001214 if lz4_ramdisks:
1215 p2 = Run(["lz4", "-l", "-12" , "--favor-decSpeed"], stdin=p1.stdout,
1216 stdout=ramdisk_img.file.fileno())
1217 else:
1218 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001219
1220 p2.wait()
1221 p1.wait()
1222 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001223 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001224
1225 return ramdisk_img
1226
1227
Steve Muckle9793cf62020-04-08 18:27:00 -07001228def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001229 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001230 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001231
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001232 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001233 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1234 we are building a two-step special image (i.e. building a recovery image to
1235 be loaded into /boot in two-step OTAs).
1236
1237 Return the image data, or None if sourcedir does not appear to contains files
1238 for building the requested image.
1239 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001240
Steve Muckle9793cf62020-04-08 18:27:00 -07001241 # "boot" or "recovery", without extension.
1242 partition_name = os.path.basename(sourcedir).lower()
1243
1244 if partition_name == "recovery":
1245 kernel = "kernel"
1246 else:
1247 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001248 kernel = kernel.replace(".img", "")
Steve Muckle9793cf62020-04-08 18:27:00 -07001249 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001250 return None
1251
1252 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001253 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001254
Doug Zongkerd5131602012-08-02 14:46:42 -07001255 if info_dict is None:
1256 info_dict = OPTIONS.info_dict
1257
Doug Zongkereef39442009-04-02 12:14:19 -07001258 img = tempfile.NamedTemporaryFile()
1259
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001260 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001261 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1262 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001263
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001264 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1265 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1266
Steve Muckle9793cf62020-04-08 18:27:00 -07001267 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001268
Benoit Fradina45a8682014-07-14 21:00:43 +02001269 fn = os.path.join(sourcedir, "second")
1270 if os.access(fn, os.F_OK):
1271 cmd.append("--second")
1272 cmd.append(fn)
1273
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001274 fn = os.path.join(sourcedir, "dtb")
1275 if os.access(fn, os.F_OK):
1276 cmd.append("--dtb")
1277 cmd.append(fn)
1278
Doug Zongker171f1cd2009-06-15 22:36:37 -07001279 fn = os.path.join(sourcedir, "cmdline")
1280 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001281 cmd.append("--cmdline")
1282 cmd.append(open(fn).read().rstrip("\n"))
1283
1284 fn = os.path.join(sourcedir, "base")
1285 if os.access(fn, os.F_OK):
1286 cmd.append("--base")
1287 cmd.append(open(fn).read().rstrip("\n"))
1288
Ying Wang4de6b5b2010-08-25 14:29:34 -07001289 fn = os.path.join(sourcedir, "pagesize")
1290 if os.access(fn, os.F_OK):
1291 cmd.append("--pagesize")
1292 cmd.append(open(fn).read().rstrip("\n"))
1293
Steve Mucklef84668e2020-03-16 19:13:46 -07001294 if partition_name == "recovery":
1295 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301296 if not args:
1297 # Fall back to "mkbootimg_args" for recovery image
1298 # in case "recovery_mkbootimg_args" is not set.
1299 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001300 else:
1301 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001302 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001303 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001304
Tao Bao76def242017-11-21 09:25:31 -08001305 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001306 if args and args.strip():
1307 cmd.extend(shlex.split(args))
1308
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001309 if has_ramdisk:
1310 cmd.extend(["--ramdisk", ramdisk_img.name])
1311
Tao Baod95e9fd2015-03-29 23:07:41 -07001312 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001313 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001314 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001315 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001316 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001317 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001318
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001319 if partition_name == "recovery":
1320 if info_dict.get("include_recovery_dtbo") == "true":
1321 fn = os.path.join(sourcedir, "recovery_dtbo")
1322 cmd.extend(["--recovery_dtbo", fn])
1323 if info_dict.get("include_recovery_acpio") == "true":
1324 fn = os.path.join(sourcedir, "recovery_acpio")
1325 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001326
Tao Bao986ee862018-10-04 15:46:16 -07001327 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001328
Tao Bao76def242017-11-21 09:25:31 -08001329 if (info_dict.get("boot_signer") == "true" and
1330 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001331 # Hard-code the path as "/boot" for two-step special recovery image (which
1332 # will be loaded into /boot during the two-step OTA).
1333 if two_step_image:
1334 path = "/boot"
1335 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001336 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001337 cmd = [OPTIONS.boot_signer_path]
1338 cmd.extend(OPTIONS.boot_signer_args)
1339 cmd.extend([path, img.name,
1340 info_dict["verity_key"] + ".pk8",
1341 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001342 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001343
Tao Baod95e9fd2015-03-29 23:07:41 -07001344 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001345 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001346 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001347 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001348 # We have switched from the prebuilt futility binary to using the tool
1349 # (futility-host) built from the source. Override the setting in the old
1350 # TF.zip.
1351 futility = info_dict["futility"]
1352 if futility.startswith("prebuilts/"):
1353 futility = "futility-host"
1354 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001355 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001356 info_dict["vboot_key"] + ".vbprivk",
1357 info_dict["vboot_subkey"] + ".vbprivk",
1358 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001359 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001360 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001361
Tao Baof3282b42015-04-01 11:21:55 -07001362 # Clean up the temp files.
1363 img_unsigned.close()
1364 img_keyblock.close()
1365
David Zeuthen8fecb282017-12-01 16:24:01 -05001366 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001367 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001368 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001369 if partition_name == "recovery":
1370 part_size = info_dict["recovery_size"]
1371 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001372 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001373 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001374 "--partition_size", str(part_size), "--partition_name",
1375 partition_name]
1376 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001377 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001378 if args and args.strip():
1379 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001380 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001381
1382 img.seek(os.SEEK_SET, 0)
1383 data = img.read()
1384
1385 if has_ramdisk:
1386 ramdisk_img.close()
1387 img.close()
1388
1389 return data
1390
1391
Doug Zongkerd5131602012-08-02 14:46:42 -07001392def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001393 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001394 """Return a File object with the desired bootable image.
1395
1396 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1397 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1398 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001399
Doug Zongker55d93282011-01-25 17:03:34 -08001400 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1401 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001402 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001403 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001404
1405 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1406 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001407 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001408 return File.FromLocalFile(name, prebuilt_path)
1409
Tao Bao32fcdab2018-10-12 10:30:39 -07001410 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001411
1412 if info_dict is None:
1413 info_dict = OPTIONS.info_dict
1414
1415 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001416 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1417 # for recovery.
1418 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1419 prebuilt_name != "boot.img" or
1420 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001421
Doug Zongker6f1d0312014-08-22 08:07:12 -07001422 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001423 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001424 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001425 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001426 if data:
1427 return File(name, data)
1428 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001429
Doug Zongkereef39442009-04-02 12:14:19 -07001430
Steve Mucklee1b10862019-07-10 10:49:37 -07001431def _BuildVendorBootImage(sourcedir, info_dict=None):
1432 """Build a vendor boot image from the specified sourcedir.
1433
1434 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1435 turn them into a vendor boot image.
1436
1437 Return the image data, or None if sourcedir does not appear to contains files
1438 for building the requested image.
1439 """
1440
1441 if info_dict is None:
1442 info_dict = OPTIONS.info_dict
1443
1444 img = tempfile.NamedTemporaryFile()
1445
J. Avila98cd4cc2020-06-10 20:09:10 +00001446 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1447 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001448
1449 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1450 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1451
1452 cmd = [mkbootimg]
1453
1454 fn = os.path.join(sourcedir, "dtb")
1455 if os.access(fn, os.F_OK):
1456 cmd.append("--dtb")
1457 cmd.append(fn)
1458
1459 fn = os.path.join(sourcedir, "vendor_cmdline")
1460 if os.access(fn, os.F_OK):
1461 cmd.append("--vendor_cmdline")
1462 cmd.append(open(fn).read().rstrip("\n"))
1463
1464 fn = os.path.join(sourcedir, "base")
1465 if os.access(fn, os.F_OK):
1466 cmd.append("--base")
1467 cmd.append(open(fn).read().rstrip("\n"))
1468
1469 fn = os.path.join(sourcedir, "pagesize")
1470 if os.access(fn, os.F_OK):
1471 cmd.append("--pagesize")
1472 cmd.append(open(fn).read().rstrip("\n"))
1473
1474 args = info_dict.get("mkbootimg_args")
1475 if args and args.strip():
1476 cmd.extend(shlex.split(args))
1477
1478 args = info_dict.get("mkbootimg_version_args")
1479 if args and args.strip():
1480 cmd.extend(shlex.split(args))
1481
1482 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1483 cmd.extend(["--vendor_boot", img.name])
1484
1485 RunAndCheckOutput(cmd)
1486
1487 # AVB: if enabled, calculate and add hash.
1488 if info_dict.get("avb_enable") == "true":
1489 avbtool = info_dict["avb_avbtool"]
1490 part_size = info_dict["vendor_boot_size"]
1491 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001492 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001493 AppendAVBSigningArgs(cmd, "vendor_boot")
1494 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1495 if args and args.strip():
1496 cmd.extend(shlex.split(args))
1497 RunAndCheckOutput(cmd)
1498
1499 img.seek(os.SEEK_SET, 0)
1500 data = img.read()
1501
1502 ramdisk_img.close()
1503 img.close()
1504
1505 return data
1506
1507
1508def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1509 info_dict=None):
1510 """Return a File object with the desired vendor boot image.
1511
1512 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1513 the source files in 'unpack_dir'/'tree_subdir'."""
1514
1515 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1516 if os.path.exists(prebuilt_path):
1517 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1518 return File.FromLocalFile(name, prebuilt_path)
1519
1520 logger.info("building image from target_files %s...", tree_subdir)
1521
1522 if info_dict is None:
1523 info_dict = OPTIONS.info_dict
1524
Kelvin Zhang0876c412020-06-23 15:06:58 -04001525 data = _BuildVendorBootImage(
1526 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001527 if data:
1528 return File(name, data)
1529 return None
1530
1531
Narayan Kamatha07bf042017-08-14 14:49:21 +01001532def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001533 """Gunzips the given gzip compressed file to a given output file."""
1534 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001535 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001536 shutil.copyfileobj(in_file, out_file)
1537
1538
Tao Bao0ff15de2019-03-20 11:26:06 -07001539def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001540 """Unzips the archive to the given directory.
1541
1542 Args:
1543 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001544 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001545 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1546 archvie. Non-matching patterns will be filtered out. If there's no match
1547 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001548 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001549 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001550 if patterns is not None:
1551 # Filter out non-matching patterns. unzip will complain otherwise.
1552 with zipfile.ZipFile(filename) as input_zip:
1553 names = input_zip.namelist()
1554 filtered = [
1555 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1556
1557 # There isn't any matching files. Don't unzip anything.
1558 if not filtered:
1559 return
1560 cmd.extend(filtered)
1561
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001562 RunAndCheckOutput(cmd)
1563
1564
Doug Zongker75f17362009-12-08 13:46:44 -08001565def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001566 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001567
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001568 Args:
1569 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1570 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1571
1572 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1573 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001574
Tao Bao1c830bf2017-12-25 10:43:47 -08001575 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001576 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001577 """
Doug Zongkereef39442009-04-02 12:14:19 -07001578
Tao Bao1c830bf2017-12-25 10:43:47 -08001579 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001580 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1581 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001582 UnzipToDir(m.group(1), tmp, pattern)
1583 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001584 filename = m.group(1)
1585 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001586 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001587
Tao Baodba59ee2018-01-09 13:21:02 -08001588 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001589
1590
Yifan Hong8a66a712019-04-04 15:37:57 -07001591def GetUserImage(which, tmpdir, input_zip,
1592 info_dict=None,
1593 allow_shared_blocks=None,
1594 hashtree_info_generator=None,
1595 reset_file_map=False):
1596 """Returns an Image object suitable for passing to BlockImageDiff.
1597
1598 This function loads the specified image from the given path. If the specified
1599 image is sparse, it also performs additional processing for OTA purpose. For
1600 example, it always adds block 0 to clobbered blocks list. It also detects
1601 files that cannot be reconstructed from the block list, for whom we should
1602 avoid applying imgdiff.
1603
1604 Args:
1605 which: The partition name.
1606 tmpdir: The directory that contains the prebuilt image and block map file.
1607 input_zip: The target-files ZIP archive.
1608 info_dict: The dict to be looked up for relevant info.
1609 allow_shared_blocks: If image is sparse, whether having shared blocks is
1610 allowed. If none, it is looked up from info_dict.
1611 hashtree_info_generator: If present and image is sparse, generates the
1612 hashtree_info for this sparse image.
1613 reset_file_map: If true and image is sparse, reset file map before returning
1614 the image.
1615 Returns:
1616 A Image object. If it is a sparse image and reset_file_map is False, the
1617 image will have file_map info loaded.
1618 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001619 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001620 info_dict = LoadInfoDict(input_zip)
1621
1622 is_sparse = info_dict.get("extfs_sparse_flag")
1623
1624 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1625 # shared blocks (i.e. some blocks will show up in multiple files' block
1626 # list). We can only allocate such shared blocks to the first "owner", and
1627 # disable imgdiff for all later occurrences.
1628 if allow_shared_blocks is None:
1629 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1630
1631 if is_sparse:
1632 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1633 hashtree_info_generator)
1634 if reset_file_map:
1635 img.ResetFileMap()
1636 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001637 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001638
1639
1640def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1641 """Returns a Image object suitable for passing to BlockImageDiff.
1642
1643 This function loads the specified non-sparse image from the given path.
1644
1645 Args:
1646 which: The partition name.
1647 tmpdir: The directory that contains the prebuilt image and block map file.
1648 Returns:
1649 A Image object.
1650 """
1651 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1652 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1653
1654 # The image and map files must have been created prior to calling
1655 # ota_from_target_files.py (since LMP).
1656 assert os.path.exists(path) and os.path.exists(mappath)
1657
Tianjie Xu41976c72019-07-03 13:57:01 -07001658 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1659
Yifan Hong8a66a712019-04-04 15:37:57 -07001660
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001661def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1662 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001663 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1664
1665 This function loads the specified sparse image from the given path, and
1666 performs additional processing for OTA purpose. For example, it always adds
1667 block 0 to clobbered blocks list. It also detects files that cannot be
1668 reconstructed from the block list, for whom we should avoid applying imgdiff.
1669
1670 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001671 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001672 tmpdir: The directory that contains the prebuilt image and block map file.
1673 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001674 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001675 hashtree_info_generator: If present, generates the hashtree_info for this
1676 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001677 Returns:
1678 A SparseImage object, with file_map info loaded.
1679 """
Tao Baoc765cca2018-01-31 17:32:40 -08001680 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1681 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1682
1683 # The image and map files must have been created prior to calling
1684 # ota_from_target_files.py (since LMP).
1685 assert os.path.exists(path) and os.path.exists(mappath)
1686
1687 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1688 # it to clobbered_blocks so that it will be written to the target
1689 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1690 clobbered_blocks = "0"
1691
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001692 image = sparse_img.SparseImage(
1693 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1694 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001695
1696 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1697 # if they contain all zeros. We can't reconstruct such a file from its block
1698 # list. Tag such entries accordingly. (Bug: 65213616)
1699 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001700 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001701 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001702 continue
1703
Tom Cherryd14b8952018-08-09 14:26:00 -07001704 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1705 # filename listed in system.map may contain an additional leading slash
1706 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1707 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001708 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001709
Tom Cherryd14b8952018-08-09 14:26:00 -07001710 # Special handling another case, where files not under /system
1711 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001712 if which == 'system' and not arcname.startswith('SYSTEM'):
1713 arcname = 'ROOT/' + arcname
1714
1715 assert arcname in input_zip.namelist(), \
1716 "Failed to find the ZIP entry for {}".format(entry)
1717
Tao Baoc765cca2018-01-31 17:32:40 -08001718 info = input_zip.getinfo(arcname)
1719 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001720
1721 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001722 # image, check the original block list to determine its completeness. Note
1723 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001724 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001725 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001726
Tao Baoc765cca2018-01-31 17:32:40 -08001727 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1728 ranges.extra['incomplete'] = True
1729
1730 return image
1731
1732
Doug Zongkereef39442009-04-02 12:14:19 -07001733def GetKeyPasswords(keylist):
1734 """Given a list of keys, prompt the user to enter passwords for
1735 those which require them. Return a {key: password} dict. password
1736 will be None if the key has no password."""
1737
Doug Zongker8ce7c252009-05-22 13:34:54 -07001738 no_passwords = []
1739 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001740 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001741 devnull = open("/dev/null", "w+b")
1742 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001743 # We don't need a password for things that aren't really keys.
1744 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001745 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001746 continue
1747
T.R. Fullhart37e10522013-03-18 10:31:26 -07001748 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001749 "-inform", "DER", "-nocrypt"],
1750 stdin=devnull.fileno(),
1751 stdout=devnull.fileno(),
1752 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001753 p.communicate()
1754 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001755 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001756 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001757 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001758 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1759 "-inform", "DER", "-passin", "pass:"],
1760 stdin=devnull.fileno(),
1761 stdout=devnull.fileno(),
1762 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001763 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001764 if p.returncode == 0:
1765 # Encrypted key with empty string as password.
1766 key_passwords[k] = ''
1767 elif stderr.startswith('Error decrypting key'):
1768 # Definitely encrypted key.
1769 # It would have said "Error reading key" if it didn't parse correctly.
1770 need_passwords.append(k)
1771 else:
1772 # Potentially, a type of key that openssl doesn't understand.
1773 # We'll let the routines in signapk.jar handle it.
1774 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001775 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001776
T.R. Fullhart37e10522013-03-18 10:31:26 -07001777 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001778 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001779 return key_passwords
1780
1781
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001782def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001783 """Gets the minSdkVersion declared in the APK.
1784
changho.shin0f125362019-07-08 10:59:00 +09001785 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001786 This can be both a decimal number (API Level) or a codename.
1787
1788 Args:
1789 apk_name: The APK filename.
1790
1791 Returns:
1792 The parsed SDK version string.
1793
1794 Raises:
1795 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001796 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001797 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001798 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001799 stderr=subprocess.PIPE)
1800 stdoutdata, stderrdata = proc.communicate()
1801 if proc.returncode != 0:
1802 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001803 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001804 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001805
Tao Baof47bf0f2018-03-21 23:28:51 -07001806 for line in stdoutdata.split("\n"):
1807 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001808 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1809 if m:
1810 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001811 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001812
1813
1814def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001815 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001816
Tao Baof47bf0f2018-03-21 23:28:51 -07001817 If minSdkVersion is set to a codename, it is translated to a number using the
1818 provided map.
1819
1820 Args:
1821 apk_name: The APK filename.
1822
1823 Returns:
1824 The parsed SDK version number.
1825
1826 Raises:
1827 ExternalError: On failing to get the min SDK version number.
1828 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001829 version = GetMinSdkVersion(apk_name)
1830 try:
1831 return int(version)
1832 except ValueError:
1833 # Not a decimal number. Codename?
1834 if version in codename_to_api_level_map:
1835 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04001836 raise ExternalError(
1837 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1838 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001839
1840
1841def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001842 codename_to_api_level_map=None, whole_file=False,
1843 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001844 """Sign the input_name zip/jar/apk, producing output_name. Use the
1845 given key and password (the latter may be None if the key does not
1846 have a password.
1847
Doug Zongker951495f2009-08-14 12:44:19 -07001848 If whole_file is true, use the "-w" option to SignApk to embed a
1849 signature that covers the whole file in the archive comment of the
1850 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001851
1852 min_api_level is the API Level (int) of the oldest platform this file may end
1853 up on. If not specified for an APK, the API Level is obtained by interpreting
1854 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1855
1856 codename_to_api_level_map is needed to translate the codename which may be
1857 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001858
1859 Caller may optionally specify extra args to be passed to SignApk, which
1860 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001861 """
Tao Bao76def242017-11-21 09:25:31 -08001862 if codename_to_api_level_map is None:
1863 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001864 if extra_signapk_args is None:
1865 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001866
Alex Klyubin9667b182015-12-10 13:38:50 -08001867 java_library_path = os.path.join(
1868 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1869
Tao Baoe95540e2016-11-08 12:08:53 -08001870 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1871 ["-Djava.library.path=" + java_library_path,
1872 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001873 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001874 if whole_file:
1875 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001876
1877 min_sdk_version = min_api_level
1878 if min_sdk_version is None:
1879 if not whole_file:
1880 min_sdk_version = GetMinSdkVersionInt(
1881 input_name, codename_to_api_level_map)
1882 if min_sdk_version is not None:
1883 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1884
T.R. Fullhart37e10522013-03-18 10:31:26 -07001885 cmd.extend([key + OPTIONS.public_key_suffix,
1886 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001887 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001888
Tao Bao73dd4f42018-10-04 16:25:33 -07001889 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001890 if password is not None:
1891 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001892 stdoutdata, _ = proc.communicate(password)
1893 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001894 raise ExternalError(
1895 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001896 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001897
Doug Zongkereef39442009-04-02 12:14:19 -07001898
Doug Zongker37974732010-09-16 17:44:38 -07001899def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001900 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001901
Tao Bao9dd909e2017-11-14 11:27:32 -08001902 For non-AVB images, raise exception if the data is too big. Print a warning
1903 if the data is nearing the maximum size.
1904
1905 For AVB images, the actual image size should be identical to the limit.
1906
1907 Args:
1908 data: A string that contains all the data for the partition.
1909 target: The partition name. The ".img" suffix is optional.
1910 info_dict: The dict to be looked up for relevant info.
1911 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001912 if target.endswith(".img"):
1913 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001914 mount_point = "/" + target
1915
Ying Wangf8824af2014-06-03 14:07:27 -07001916 fs_type = None
1917 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001918 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001919 if mount_point == "/userdata":
1920 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001921 p = info_dict["fstab"][mount_point]
1922 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001923 device = p.device
1924 if "/" in device:
1925 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001926 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001927 if not fs_type or not limit:
1928 return
Doug Zongkereef39442009-04-02 12:14:19 -07001929
Andrew Boie0f9aec82012-02-14 09:32:52 -08001930 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001931 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1932 # path.
1933 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1934 if size != limit:
1935 raise ExternalError(
1936 "Mismatching image size for %s: expected %d actual %d" % (
1937 target, limit, size))
1938 else:
1939 pct = float(size) * 100.0 / limit
1940 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1941 if pct >= 99.0:
1942 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04001943
1944 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001945 logger.warning("\n WARNING: %s\n", msg)
1946 else:
1947 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001948
1949
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001950def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001951 """Parses the APK certs info from a given target-files zip.
1952
1953 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1954 tuple with the following elements: (1) a dictionary that maps packages to
1955 certs (based on the "certificate" and "private_key" attributes in the file;
1956 (2) a string representing the extension of compressed APKs in the target files
1957 (e.g ".gz", ".bro").
1958
1959 Args:
1960 tf_zip: The input target_files ZipFile (already open).
1961
1962 Returns:
1963 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1964 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1965 no compressed APKs.
1966 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001967 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001968 compressed_extension = None
1969
Tao Bao0f990332017-09-08 19:02:54 -07001970 # META/apkcerts.txt contains the info for _all_ the packages known at build
1971 # time. Filter out the ones that are not installed.
1972 installed_files = set()
1973 for name in tf_zip.namelist():
1974 basename = os.path.basename(name)
1975 if basename:
1976 installed_files.add(basename)
1977
Tao Baoda30cfa2017-12-01 16:19:46 -08001978 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001979 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001980 if not line:
1981 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001982 m = re.match(
1983 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001984 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1985 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001986 line)
1987 if not m:
1988 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001989
Tao Bao818ddf52018-01-05 11:17:34 -08001990 matches = m.groupdict()
1991 cert = matches["CERT"]
1992 privkey = matches["PRIVKEY"]
1993 name = matches["NAME"]
1994 this_compressed_extension = matches["COMPRESSED"]
1995
1996 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1997 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1998 if cert in SPECIAL_CERT_STRINGS and not privkey:
1999 certmap[name] = cert
2000 elif (cert.endswith(OPTIONS.public_key_suffix) and
2001 privkey.endswith(OPTIONS.private_key_suffix) and
2002 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2003 certmap[name] = cert[:-public_key_suffix_len]
2004 else:
2005 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2006
2007 if not this_compressed_extension:
2008 continue
2009
2010 # Only count the installed files.
2011 filename = name + '.' + this_compressed_extension
2012 if filename not in installed_files:
2013 continue
2014
2015 # Make sure that all the values in the compression map have the same
2016 # extension. We don't support multiple compression methods in the same
2017 # system image.
2018 if compressed_extension:
2019 if this_compressed_extension != compressed_extension:
2020 raise ValueError(
2021 "Multiple compressed extensions: {} vs {}".format(
2022 compressed_extension, this_compressed_extension))
2023 else:
2024 compressed_extension = this_compressed_extension
2025
2026 return (certmap,
2027 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002028
2029
Doug Zongkereef39442009-04-02 12:14:19 -07002030COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002031Global options
2032
2033 -p (--path) <dir>
2034 Prepend <dir>/bin to the list of places to search for binaries run by this
2035 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002036
Doug Zongker05d3dea2009-06-22 11:32:31 -07002037 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002038 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002039
Tao Bao30df8b42018-04-23 15:32:53 -07002040 -x (--extra) <key=value>
2041 Add a key/value pair to the 'extras' dict, which device-specific extension
2042 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002043
Doug Zongkereef39442009-04-02 12:14:19 -07002044 -v (--verbose)
2045 Show command lines being executed.
2046
2047 -h (--help)
2048 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002049
2050 --logfile <file>
2051 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002052"""
2053
Kelvin Zhang0876c412020-06-23 15:06:58 -04002054
Doug Zongkereef39442009-04-02 12:14:19 -07002055def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002056 print(docstring.rstrip("\n"))
2057 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002058
2059
2060def ParseOptions(argv,
2061 docstring,
2062 extra_opts="", extra_long_opts=(),
2063 extra_option_handler=None):
2064 """Parse the options in argv and return any arguments that aren't
2065 flags. docstring is the calling module's docstring, to be displayed
2066 for errors and -h. extra_opts and extra_long_opts are for flags
2067 defined by the caller, which are processed by passing them to
2068 extra_option_handler."""
2069
2070 try:
2071 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002072 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002073 ["help", "verbose", "path=", "signapk_path=",
2074 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002075 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002076 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2077 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002078 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2079 "aftl_key_path=", "aftl_manufacturer_key_path=",
2080 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002081 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002082 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002083 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002084 sys.exit(2)
2085
Doug Zongkereef39442009-04-02 12:14:19 -07002086 for o, a in opts:
2087 if o in ("-h", "--help"):
2088 Usage(docstring)
2089 sys.exit()
2090 elif o in ("-v", "--verbose"):
2091 OPTIONS.verbose = True
2092 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002093 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002094 elif o in ("--signapk_path",):
2095 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002096 elif o in ("--signapk_shared_library_path",):
2097 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002098 elif o in ("--extra_signapk_args",):
2099 OPTIONS.extra_signapk_args = shlex.split(a)
2100 elif o in ("--java_path",):
2101 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002102 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002103 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002104 elif o in ("--android_jar_path",):
2105 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002106 elif o in ("--public_key_suffix",):
2107 OPTIONS.public_key_suffix = a
2108 elif o in ("--private_key_suffix",):
2109 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002110 elif o in ("--boot_signer_path",):
2111 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002112 elif o in ("--boot_signer_args",):
2113 OPTIONS.boot_signer_args = shlex.split(a)
2114 elif o in ("--verity_signer_path",):
2115 OPTIONS.verity_signer_path = a
2116 elif o in ("--verity_signer_args",):
2117 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002118 elif o in ("--aftl_tool_path",):
2119 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002120 elif o in ("--aftl_server",):
2121 OPTIONS.aftl_server = a
2122 elif o in ("--aftl_key_path",):
2123 OPTIONS.aftl_key_path = a
2124 elif o in ("--aftl_manufacturer_key_path",):
2125 OPTIONS.aftl_manufacturer_key_path = a
2126 elif o in ("--aftl_signer_helper",):
2127 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002128 elif o in ("-s", "--device_specific"):
2129 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002130 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002131 key, value = a.split("=", 1)
2132 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002133 elif o in ("--logfile",):
2134 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002135 else:
2136 if extra_option_handler is None or not extra_option_handler(o, a):
2137 assert False, "unknown option \"%s\"" % (o,)
2138
Doug Zongker85448772014-09-09 14:59:20 -07002139 if OPTIONS.search_path:
2140 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2141 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002142
2143 return args
2144
2145
Tao Bao4c851b12016-09-19 13:54:38 -07002146def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002147 """Make a temp file and add it to the list of things to be deleted
2148 when Cleanup() is called. Return the filename."""
2149 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2150 os.close(fd)
2151 OPTIONS.tempfiles.append(fn)
2152 return fn
2153
2154
Tao Bao1c830bf2017-12-25 10:43:47 -08002155def MakeTempDir(prefix='tmp', suffix=''):
2156 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2157
2158 Returns:
2159 The absolute pathname of the new directory.
2160 """
2161 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2162 OPTIONS.tempfiles.append(dir_name)
2163 return dir_name
2164
2165
Doug Zongkereef39442009-04-02 12:14:19 -07002166def Cleanup():
2167 for i in OPTIONS.tempfiles:
2168 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002169 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002170 else:
2171 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002172 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002173
2174
2175class PasswordManager(object):
2176 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002177 self.editor = os.getenv("EDITOR")
2178 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002179
2180 def GetPasswords(self, items):
2181 """Get passwords corresponding to each string in 'items',
2182 returning a dict. (The dict may have keys in addition to the
2183 values in 'items'.)
2184
2185 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2186 user edit that file to add more needed passwords. If no editor is
2187 available, or $ANDROID_PW_FILE isn't define, prompts the user
2188 interactively in the ordinary way.
2189 """
2190
2191 current = self.ReadFile()
2192
2193 first = True
2194 while True:
2195 missing = []
2196 for i in items:
2197 if i not in current or not current[i]:
2198 missing.append(i)
2199 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002200 if not missing:
2201 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002202
2203 for i in missing:
2204 current[i] = ""
2205
2206 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002207 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002208 if sys.version_info[0] >= 3:
2209 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002210 answer = raw_input("try to edit again? [y]> ").strip()
2211 if answer and answer[0] not in 'yY':
2212 raise RuntimeError("key passwords unavailable")
2213 first = False
2214
2215 current = self.UpdateAndReadFile(current)
2216
Kelvin Zhang0876c412020-06-23 15:06:58 -04002217 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002218 """Prompt the user to enter a value (password) for each key in
2219 'current' whose value is fales. Returns a new dict with all the
2220 values.
2221 """
2222 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002223 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002224 if v:
2225 result[k] = v
2226 else:
2227 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002228 result[k] = getpass.getpass(
2229 "Enter password for %s key> " % k).strip()
2230 if result[k]:
2231 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002232 return result
2233
2234 def UpdateAndReadFile(self, current):
2235 if not self.editor or not self.pwfile:
2236 return self.PromptResult(current)
2237
2238 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002239 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002240 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2241 f.write("# (Additional spaces are harmless.)\n\n")
2242
2243 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002244 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002245 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002246 f.write("[[[ %s ]]] %s\n" % (v, k))
2247 if not v and first_line is None:
2248 # position cursor on first line with no password.
2249 first_line = i + 4
2250 f.close()
2251
Tao Bao986ee862018-10-04 15:46:16 -07002252 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002253
2254 return self.ReadFile()
2255
2256 def ReadFile(self):
2257 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002258 if self.pwfile is None:
2259 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002260 try:
2261 f = open(self.pwfile, "r")
2262 for line in f:
2263 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002264 if not line or line[0] == '#':
2265 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002266 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2267 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002268 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002269 else:
2270 result[m.group(2)] = m.group(1)
2271 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002272 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002273 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002274 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002275 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002276
2277
Dan Albert8e0178d2015-01-27 15:53:15 -08002278def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2279 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002280
2281 # http://b/18015246
2282 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2283 # for files larger than 2GiB. We can work around this by adjusting their
2284 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2285 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2286 # it isn't clear to me exactly what circumstances cause this).
2287 # `zipfile.write()` must be used directly to work around this.
2288 #
2289 # This mess can be avoided if we port to python3.
2290 saved_zip64_limit = zipfile.ZIP64_LIMIT
2291 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2292
2293 if compress_type is None:
2294 compress_type = zip_file.compression
2295 if arcname is None:
2296 arcname = filename
2297
2298 saved_stat = os.stat(filename)
2299
2300 try:
2301 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2302 # file to be zipped and reset it when we're done.
2303 os.chmod(filename, perms)
2304
2305 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002306 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2307 # intentional. zip stores datetimes in local time without a time zone
2308 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2309 # in the zip archive.
2310 local_epoch = datetime.datetime.fromtimestamp(0)
2311 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002312 os.utime(filename, (timestamp, timestamp))
2313
2314 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2315 finally:
2316 os.chmod(filename, saved_stat.st_mode)
2317 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2318 zipfile.ZIP64_LIMIT = saved_zip64_limit
2319
2320
Tao Bao58c1b962015-05-20 09:32:18 -07002321def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002322 compress_type=None):
2323 """Wrap zipfile.writestr() function to work around the zip64 limit.
2324
2325 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2326 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2327 when calling crc32(bytes).
2328
2329 But it still works fine to write a shorter string into a large zip file.
2330 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2331 when we know the string won't be too long.
2332 """
2333
2334 saved_zip64_limit = zipfile.ZIP64_LIMIT
2335 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2336
2337 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2338 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002339 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002340 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002341 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002342 else:
Tao Baof3282b42015-04-01 11:21:55 -07002343 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002344 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2345 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2346 # such a case (since
2347 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2348 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2349 # permission bits. We follow the logic in Python 3 to get consistent
2350 # behavior between using the two versions.
2351 if not zinfo.external_attr:
2352 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002353
2354 # If compress_type is given, it overrides the value in zinfo.
2355 if compress_type is not None:
2356 zinfo.compress_type = compress_type
2357
Tao Bao58c1b962015-05-20 09:32:18 -07002358 # If perms is given, it has a priority.
2359 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002360 # If perms doesn't set the file type, mark it as a regular file.
2361 if perms & 0o770000 == 0:
2362 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002363 zinfo.external_attr = perms << 16
2364
Tao Baof3282b42015-04-01 11:21:55 -07002365 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002366 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2367
Dan Albert8b72aef2015-03-23 19:13:21 -07002368 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002369 zipfile.ZIP64_LIMIT = saved_zip64_limit
2370
2371
Tao Bao89d7ab22017-12-14 17:05:33 -08002372def ZipDelete(zip_filename, entries):
2373 """Deletes entries from a ZIP file.
2374
2375 Since deleting entries from a ZIP file is not supported, it shells out to
2376 'zip -d'.
2377
2378 Args:
2379 zip_filename: The name of the ZIP file.
2380 entries: The name of the entry, or the list of names to be deleted.
2381
2382 Raises:
2383 AssertionError: In case of non-zero return from 'zip'.
2384 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002385 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002386 entries = [entries]
2387 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002388 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002389
2390
Tao Baof3282b42015-04-01 11:21:55 -07002391def ZipClose(zip_file):
2392 # http://b/18015246
2393 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2394 # central directory.
2395 saved_zip64_limit = zipfile.ZIP64_LIMIT
2396 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2397
2398 zip_file.close()
2399
2400 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002401
2402
2403class DeviceSpecificParams(object):
2404 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002405
Doug Zongker05d3dea2009-06-22 11:32:31 -07002406 def __init__(self, **kwargs):
2407 """Keyword arguments to the constructor become attributes of this
2408 object, which is passed to all functions in the device-specific
2409 module."""
Tao Bao38884282019-07-10 22:20:56 -07002410 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002411 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002412 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002413
2414 if self.module is None:
2415 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002416 if not path:
2417 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002418 try:
2419 if os.path.isdir(path):
2420 info = imp.find_module("releasetools", [path])
2421 else:
2422 d, f = os.path.split(path)
2423 b, x = os.path.splitext(f)
2424 if x == ".py":
2425 f = b
2426 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002427 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002428 self.module = imp.load_module("device_specific", *info)
2429 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002430 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002431
2432 def _DoCall(self, function_name, *args, **kwargs):
2433 """Call the named function in the device-specific module, passing
2434 the given args and kwargs. The first argument to the call will be
2435 the DeviceSpecific object itself. If there is no module, or the
2436 module does not define the function, return the value of the
2437 'default' kwarg (which itself defaults to None)."""
2438 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002439 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002440 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2441
2442 def FullOTA_Assertions(self):
2443 """Called after emitting the block of assertions at the top of a
2444 full OTA package. Implementations can add whatever additional
2445 assertions they like."""
2446 return self._DoCall("FullOTA_Assertions")
2447
Doug Zongkere5ff5902012-01-17 10:55:37 -08002448 def FullOTA_InstallBegin(self):
2449 """Called at the start of full OTA installation."""
2450 return self._DoCall("FullOTA_InstallBegin")
2451
Yifan Hong10c530d2018-12-27 17:34:18 -08002452 def FullOTA_GetBlockDifferences(self):
2453 """Called during full OTA installation and verification.
2454 Implementation should return a list of BlockDifference objects describing
2455 the update on each additional partitions.
2456 """
2457 return self._DoCall("FullOTA_GetBlockDifferences")
2458
Doug Zongker05d3dea2009-06-22 11:32:31 -07002459 def FullOTA_InstallEnd(self):
2460 """Called at the end of full OTA installation; typically this is
2461 used to install the image for the device's baseband processor."""
2462 return self._DoCall("FullOTA_InstallEnd")
2463
2464 def IncrementalOTA_Assertions(self):
2465 """Called after emitting the block of assertions at the top of an
2466 incremental OTA package. Implementations can add whatever
2467 additional assertions they like."""
2468 return self._DoCall("IncrementalOTA_Assertions")
2469
Doug Zongkere5ff5902012-01-17 10:55:37 -08002470 def IncrementalOTA_VerifyBegin(self):
2471 """Called at the start of the verification phase of incremental
2472 OTA installation; additional checks can be placed here to abort
2473 the script before any changes are made."""
2474 return self._DoCall("IncrementalOTA_VerifyBegin")
2475
Doug Zongker05d3dea2009-06-22 11:32:31 -07002476 def IncrementalOTA_VerifyEnd(self):
2477 """Called at the end of the verification phase of incremental OTA
2478 installation; additional checks can be placed here to abort the
2479 script before any changes are made."""
2480 return self._DoCall("IncrementalOTA_VerifyEnd")
2481
Doug Zongkere5ff5902012-01-17 10:55:37 -08002482 def IncrementalOTA_InstallBegin(self):
2483 """Called at the start of incremental OTA installation (after
2484 verification is complete)."""
2485 return self._DoCall("IncrementalOTA_InstallBegin")
2486
Yifan Hong10c530d2018-12-27 17:34:18 -08002487 def IncrementalOTA_GetBlockDifferences(self):
2488 """Called during incremental OTA installation and verification.
2489 Implementation should return a list of BlockDifference objects describing
2490 the update on each additional partitions.
2491 """
2492 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2493
Doug Zongker05d3dea2009-06-22 11:32:31 -07002494 def IncrementalOTA_InstallEnd(self):
2495 """Called at the end of incremental OTA installation; typically
2496 this is used to install the image for the device's baseband
2497 processor."""
2498 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002499
Tao Bao9bc6bb22015-11-09 16:58:28 -08002500 def VerifyOTA_Assertions(self):
2501 return self._DoCall("VerifyOTA_Assertions")
2502
Tao Bao76def242017-11-21 09:25:31 -08002503
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002504class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002505 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002506 self.name = name
2507 self.data = data
2508 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002509 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002510 self.sha1 = sha1(data).hexdigest()
2511
2512 @classmethod
2513 def FromLocalFile(cls, name, diskname):
2514 f = open(diskname, "rb")
2515 data = f.read()
2516 f.close()
2517 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002518
2519 def WriteToTemp(self):
2520 t = tempfile.NamedTemporaryFile()
2521 t.write(self.data)
2522 t.flush()
2523 return t
2524
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002525 def WriteToDir(self, d):
2526 with open(os.path.join(d, self.name), "wb") as fp:
2527 fp.write(self.data)
2528
Geremy Condra36bd3652014-02-06 19:45:10 -08002529 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002530 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002531
Tao Bao76def242017-11-21 09:25:31 -08002532
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002533DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002534 ".gz": "imgdiff",
2535 ".zip": ["imgdiff", "-z"],
2536 ".jar": ["imgdiff", "-z"],
2537 ".apk": ["imgdiff", "-z"],
2538 ".img": "imgdiff",
2539}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002540
Tao Bao76def242017-11-21 09:25:31 -08002541
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002542class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002543 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002544 self.tf = tf
2545 self.sf = sf
2546 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002547 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002548
2549 def ComputePatch(self):
2550 """Compute the patch (as a string of data) needed to turn sf into
2551 tf. Returns the same tuple as GetPatch()."""
2552
2553 tf = self.tf
2554 sf = self.sf
2555
Doug Zongker24cd2802012-08-14 16:36:15 -07002556 if self.diff_program:
2557 diff_program = self.diff_program
2558 else:
2559 ext = os.path.splitext(tf.name)[1]
2560 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002561
2562 ttemp = tf.WriteToTemp()
2563 stemp = sf.WriteToTemp()
2564
2565 ext = os.path.splitext(tf.name)[1]
2566
2567 try:
2568 ptemp = tempfile.NamedTemporaryFile()
2569 if isinstance(diff_program, list):
2570 cmd = copy.copy(diff_program)
2571 else:
2572 cmd = [diff_program]
2573 cmd.append(stemp.name)
2574 cmd.append(ttemp.name)
2575 cmd.append(ptemp.name)
2576 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002577 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002578
Doug Zongkerf8340082014-08-05 10:39:37 -07002579 def run():
2580 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002581 if e:
2582 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002583 th = threading.Thread(target=run)
2584 th.start()
2585 th.join(timeout=300) # 5 mins
2586 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002587 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002588 p.terminate()
2589 th.join(5)
2590 if th.is_alive():
2591 p.kill()
2592 th.join()
2593
Tianjie Xua2a9f992018-01-05 15:15:54 -08002594 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002595 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002596 self.patch = None
2597 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002598 diff = ptemp.read()
2599 finally:
2600 ptemp.close()
2601 stemp.close()
2602 ttemp.close()
2603
2604 self.patch = diff
2605 return self.tf, self.sf, self.patch
2606
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002607 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002608 """Returns a tuple of (target_file, source_file, patch_data).
2609
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002610 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002611 computing the patch failed.
2612 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002613 return self.tf, self.sf, self.patch
2614
2615
2616def ComputeDifferences(diffs):
2617 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002618 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002619
2620 # Do the largest files first, to try and reduce the long-pole effect.
2621 by_size = [(i.tf.size, i) for i in diffs]
2622 by_size.sort(reverse=True)
2623 by_size = [i[1] for i in by_size]
2624
2625 lock = threading.Lock()
2626 diff_iter = iter(by_size) # accessed under lock
2627
2628 def worker():
2629 try:
2630 lock.acquire()
2631 for d in diff_iter:
2632 lock.release()
2633 start = time.time()
2634 d.ComputePatch()
2635 dur = time.time() - start
2636 lock.acquire()
2637
2638 tf, sf, patch = d.GetPatch()
2639 if sf.name == tf.name:
2640 name = tf.name
2641 else:
2642 name = "%s (%s)" % (tf.name, sf.name)
2643 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002644 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002645 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002646 logger.info(
2647 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2648 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002649 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002650 except Exception:
2651 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002652 raise
2653
2654 # start worker threads; wait for them all to finish.
2655 threads = [threading.Thread(target=worker)
2656 for i in range(OPTIONS.worker_threads)]
2657 for th in threads:
2658 th.start()
2659 while threads:
2660 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002661
2662
Dan Albert8b72aef2015-03-23 19:13:21 -07002663class BlockDifference(object):
2664 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002665 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002666 self.tgt = tgt
2667 self.src = src
2668 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002669 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002670 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002671
Tao Baodd2a5892015-03-12 12:32:37 -07002672 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002673 version = max(
2674 int(i) for i in
2675 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002676 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002677 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002678
Tianjie Xu41976c72019-07-03 13:57:01 -07002679 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2680 version=self.version,
2681 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002682 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002683 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002684 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002685 self.touched_src_ranges = b.touched_src_ranges
2686 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002687
Yifan Hong10c530d2018-12-27 17:34:18 -08002688 # On devices with dynamic partitions, for new partitions,
2689 # src is None but OPTIONS.source_info_dict is not.
2690 if OPTIONS.source_info_dict is None:
2691 is_dynamic_build = OPTIONS.info_dict.get(
2692 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002693 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002694 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002695 is_dynamic_build = OPTIONS.source_info_dict.get(
2696 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002697 is_dynamic_source = partition in shlex.split(
2698 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002699
Yifan Hongbb2658d2019-01-25 12:30:58 -08002700 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002701 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2702
Yifan Hongbb2658d2019-01-25 12:30:58 -08002703 # For dynamic partitions builds, check partition list in both source
2704 # and target build because new partitions may be added, and existing
2705 # partitions may be removed.
2706 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2707
Yifan Hong10c530d2018-12-27 17:34:18 -08002708 if is_dynamic:
2709 self.device = 'map_partition("%s")' % partition
2710 else:
2711 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002712 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2713 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002714 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002715 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2716 OPTIONS.source_info_dict)
2717 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002718
Tao Baod8d14be2016-02-04 14:26:02 -08002719 @property
2720 def required_cache(self):
2721 return self._required_cache
2722
Tao Bao76def242017-11-21 09:25:31 -08002723 def WriteScript(self, script, output_zip, progress=None,
2724 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002725 if not self.src:
2726 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002727 script.Print("Patching %s image unconditionally..." % (self.partition,))
2728 else:
2729 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002730
Dan Albert8b72aef2015-03-23 19:13:21 -07002731 if progress:
2732 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002733 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002734
2735 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002736 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002737
Tao Bao9bc6bb22015-11-09 16:58:28 -08002738 def WriteStrictVerifyScript(self, script):
2739 """Verify all the blocks in the care_map, including clobbered blocks.
2740
2741 This differs from the WriteVerifyScript() function: a) it prints different
2742 error messages; b) it doesn't allow half-way updated images to pass the
2743 verification."""
2744
2745 partition = self.partition
2746 script.Print("Verifying %s..." % (partition,))
2747 ranges = self.tgt.care_map
2748 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002749 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002750 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2751 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002752 self.device, ranges_str,
2753 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002754 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002755 script.AppendExtra("")
2756
Tao Baod522bdc2016-04-12 15:53:16 -07002757 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002758 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002759
2760 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002761 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002762 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002763
2764 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002765 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002766 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002767 ranges = self.touched_src_ranges
2768 expected_sha1 = self.touched_src_sha1
2769 else:
2770 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2771 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002772
2773 # No blocks to be checked, skipping.
2774 if not ranges:
2775 return
2776
Tao Bao5ece99d2015-05-12 11:42:31 -07002777 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002778 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002779 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002780 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2781 '"%s.patch.dat")) then' % (
2782 self.device, ranges_str, expected_sha1,
2783 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002784 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002785 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002786
Tianjie Xufc3422a2015-12-15 11:53:59 -08002787 if self.version >= 4:
2788
2789 # Bug: 21124327
2790 # When generating incrementals for the system and vendor partitions in
2791 # version 4 or newer, explicitly check the first block (which contains
2792 # the superblock) of the partition to see if it's what we expect. If
2793 # this check fails, give an explicit log message about the partition
2794 # having been remounted R/W (the most likely explanation).
2795 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002796 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002797
2798 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002799 if partition == "system":
2800 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2801 else:
2802 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002803 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002804 'ifelse (block_image_recover({device}, "{ranges}") && '
2805 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002806 'package_extract_file("{partition}.transfer.list"), '
2807 '"{partition}.new.dat", "{partition}.patch.dat"), '
2808 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002809 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002810 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002811 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002812
Tao Baodd2a5892015-03-12 12:32:37 -07002813 # Abort the OTA update. Note that the incremental OTA cannot be applied
2814 # even if it may match the checksum of the target partition.
2815 # a) If version < 3, operations like move and erase will make changes
2816 # unconditionally and damage the partition.
2817 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002818 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002819 if partition == "system":
2820 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2821 else:
2822 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2823 script.AppendExtra((
2824 'abort("E%d: %s partition has unexpected contents");\n'
2825 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002826
Yifan Hong10c530d2018-12-27 17:34:18 -08002827 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002828 partition = self.partition
2829 script.Print('Verifying the updated %s image...' % (partition,))
2830 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2831 ranges = self.tgt.care_map
2832 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002833 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002834 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002835 self.device, ranges_str,
2836 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002837
2838 # Bug: 20881595
2839 # Verify that extended blocks are really zeroed out.
2840 if self.tgt.extended:
2841 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002842 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002843 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002844 self.device, ranges_str,
2845 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002846 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002847 if partition == "system":
2848 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2849 else:
2850 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002851 script.AppendExtra(
2852 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002853 ' abort("E%d: %s partition has unexpected non-zero contents after '
2854 'OTA update");\n'
2855 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002856 else:
2857 script.Print('Verified the updated %s image.' % (partition,))
2858
Tianjie Xu209db462016-05-24 17:34:52 -07002859 if partition == "system":
2860 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2861 else:
2862 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2863
Tao Bao5fcaaef2015-06-01 13:40:49 -07002864 script.AppendExtra(
2865 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002866 ' abort("E%d: %s partition has unexpected contents after OTA '
2867 'update");\n'
2868 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002869
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002870 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002871 ZipWrite(output_zip,
2872 '{}.transfer.list'.format(self.path),
2873 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002874
Tao Bao76def242017-11-21 09:25:31 -08002875 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2876 # its size. Quailty 9 almost triples the compression time but doesn't
2877 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002878 # zip | brotli(quality 6) | brotli(quality 9)
2879 # compressed_size: 942M | 869M (~8% reduced) | 854M
2880 # compression_time: 75s | 265s | 719s
2881 # decompression_time: 15s | 25s | 25s
2882
2883 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002884 brotli_cmd = ['brotli', '--quality=6',
2885 '--output={}.new.dat.br'.format(self.path),
2886 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002887 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002888 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002889
2890 new_data_name = '{}.new.dat.br'.format(self.partition)
2891 ZipWrite(output_zip,
2892 '{}.new.dat.br'.format(self.path),
2893 new_data_name,
2894 compress_type=zipfile.ZIP_STORED)
2895 else:
2896 new_data_name = '{}.new.dat'.format(self.partition)
2897 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2898
Dan Albert8e0178d2015-01-27 15:53:15 -08002899 ZipWrite(output_zip,
2900 '{}.patch.dat'.format(self.path),
2901 '{}.patch.dat'.format(self.partition),
2902 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002903
Tianjie Xu209db462016-05-24 17:34:52 -07002904 if self.partition == "system":
2905 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2906 else:
2907 code = ErrorCode.VENDOR_UPDATE_FAILURE
2908
Yifan Hong10c530d2018-12-27 17:34:18 -08002909 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002910 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002911 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002912 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002913 device=self.device, partition=self.partition,
2914 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002915 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002916
Kelvin Zhang0876c412020-06-23 15:06:58 -04002917 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002918 data = source.ReadRangeSet(ranges)
2919 ctx = sha1()
2920
2921 for p in data:
2922 ctx.update(p)
2923
2924 return ctx.hexdigest()
2925
Kelvin Zhang0876c412020-06-23 15:06:58 -04002926 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07002927 """Return the hash value for all zero blocks."""
2928 zero_block = '\x00' * 4096
2929 ctx = sha1()
2930 for _ in range(num_blocks):
2931 ctx.update(zero_block)
2932
2933 return ctx.hexdigest()
2934
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002935
Tianjie Xu41976c72019-07-03 13:57:01 -07002936# Expose these two classes to support vendor-specific scripts
2937DataImage = images.DataImage
2938EmptyImage = images.EmptyImage
2939
Tao Bao76def242017-11-21 09:25:31 -08002940
Doug Zongker96a57e72010-09-26 14:57:41 -07002941# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002942PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002943 "ext4": "EMMC",
2944 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002945 "f2fs": "EMMC",
2946 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002947}
Doug Zongker96a57e72010-09-26 14:57:41 -07002948
Kelvin Zhang0876c412020-06-23 15:06:58 -04002949
Yifan Hongbdb32012020-05-07 12:38:53 -07002950def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2951 """
2952 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2953 backwards compatibility. It aborts if the fstab entry has slotselect option
2954 (unless check_no_slot is explicitly set to False).
2955 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002956 fstab = info["fstab"]
2957 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07002958 if check_no_slot:
2959 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002960 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002961 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2962 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002963 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002964
2965
Yifan Hongbdb32012020-05-07 12:38:53 -07002966def GetTypeAndDeviceExpr(mount_point, info):
2967 """
2968 Return the filesystem of the partition, and an edify expression that evaluates
2969 to the device at runtime.
2970 """
2971 fstab = info["fstab"]
2972 if fstab:
2973 p = fstab[mount_point]
2974 device_expr = '"%s"' % fstab[mount_point].device
2975 if p.slotselect:
2976 device_expr = 'add_slot_suffix(%s)' % device_expr
2977 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002978 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07002979
2980
2981def GetEntryForDevice(fstab, device):
2982 """
2983 Returns:
2984 The first entry in fstab whose device is the given value.
2985 """
2986 if not fstab:
2987 return None
2988 for mount_point in fstab:
2989 if fstab[mount_point].device == device:
2990 return fstab[mount_point]
2991 return None
2992
Kelvin Zhang0876c412020-06-23 15:06:58 -04002993
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002994def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002995 """Parses and converts a PEM-encoded certificate into DER-encoded.
2996
2997 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2998
2999 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003000 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003001 """
3002 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003003 save = False
3004 for line in data.split("\n"):
3005 if "--END CERTIFICATE--" in line:
3006 break
3007 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003008 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003009 if "--BEGIN CERTIFICATE--" in line:
3010 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003011 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003012 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003013
Tao Bao04e1f012018-02-04 12:13:35 -08003014
3015def ExtractPublicKey(cert):
3016 """Extracts the public key (PEM-encoded) from the given certificate file.
3017
3018 Args:
3019 cert: The certificate filename.
3020
3021 Returns:
3022 The public key string.
3023
3024 Raises:
3025 AssertionError: On non-zero return from 'openssl'.
3026 """
3027 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3028 # While openssl 1.1 writes the key into the given filename followed by '-out',
3029 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3030 # stdout instead.
3031 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3032 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3033 pubkey, stderrdata = proc.communicate()
3034 assert proc.returncode == 0, \
3035 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3036 return pubkey
3037
3038
Tao Bao1ac886e2019-06-26 11:58:22 -07003039def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003040 """Extracts the AVB public key from the given public or private key.
3041
3042 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003043 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003044 key: The input key file, which should be PEM-encoded public or private key.
3045
3046 Returns:
3047 The path to the extracted AVB public key file.
3048 """
3049 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3050 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003051 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003052 return output
3053
3054
Doug Zongker412c02f2014-02-13 10:58:24 -08003055def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3056 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003057 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003058
Tao Bao6d5d6232018-03-09 17:04:42 -08003059 Most of the space in the boot and recovery images is just the kernel, which is
3060 identical for the two, so the resulting patch should be efficient. Add it to
3061 the output zip, along with a shell script that is run from init.rc on first
3062 boot to actually do the patching and install the new recovery image.
3063
3064 Args:
3065 input_dir: The top-level input directory of the target-files.zip.
3066 output_sink: The callback function that writes the result.
3067 recovery_img: File object for the recovery image.
3068 boot_img: File objects for the boot image.
3069 info_dict: A dict returned by common.LoadInfoDict() on the input
3070 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003071 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003072 if info_dict is None:
3073 info_dict = OPTIONS.info_dict
3074
Tao Bao6d5d6232018-03-09 17:04:42 -08003075 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003076 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3077
3078 if board_uses_vendorimage:
3079 # In this case, the output sink is rooted at VENDOR
3080 recovery_img_path = "etc/recovery.img"
3081 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3082 sh_dir = "bin"
3083 else:
3084 # In this case the output sink is rooted at SYSTEM
3085 recovery_img_path = "vendor/etc/recovery.img"
3086 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3087 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003088
Tao Baof2cffbd2015-07-22 12:33:18 -07003089 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003090 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003091
3092 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003093 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003094 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003095 # With system-root-image, boot and recovery images will have mismatching
3096 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3097 # to handle such a case.
3098 if system_root_image:
3099 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003100 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003101 assert not os.path.exists(path)
3102 else:
3103 diff_program = ["imgdiff"]
3104 if os.path.exists(path):
3105 diff_program.append("-b")
3106 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003107 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003108 else:
3109 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003110
3111 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3112 _, _, patch = d.ComputePatch()
3113 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003114
Dan Albertebb19aa2015-03-27 19:11:53 -07003115 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003116 # The following GetTypeAndDevice()s need to use the path in the target
3117 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003118 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3119 check_no_slot=False)
3120 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3121 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003122 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003123 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003124
Tao Baof2cffbd2015-07-22 12:33:18 -07003125 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003126
3127 # Note that we use /vendor to refer to the recovery resources. This will
3128 # work for a separate vendor partition mounted at /vendor or a
3129 # /system/vendor subdirectory on the system partition, for which init will
3130 # create a symlink from /vendor to /system/vendor.
3131
3132 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003133if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3134 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003135 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003136 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3137 log -t recovery "Installing new recovery image: succeeded" || \\
3138 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003139else
3140 log -t recovery "Recovery image already installed"
3141fi
3142""" % {'type': recovery_type,
3143 'device': recovery_device,
3144 'sha1': recovery_img.sha1,
3145 'size': recovery_img.size}
3146 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003147 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003148if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3149 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003150 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003151 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3152 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3153 log -t recovery "Installing new recovery image: succeeded" || \\
3154 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003155else
3156 log -t recovery "Recovery image already installed"
3157fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003158""" % {'boot_size': boot_img.size,
3159 'boot_sha1': boot_img.sha1,
3160 'recovery_size': recovery_img.size,
3161 'recovery_sha1': recovery_img.sha1,
3162 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003163 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
3164 'recovery_type': recovery_type + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003165 'recovery_device': recovery_device,
3166 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003167
Bill Peckhame868aec2019-09-17 17:06:47 -07003168 # The install script location moved from /system/etc to /system/bin in the L
3169 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3170 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003171
Tao Bao32fcdab2018-10-12 10:30:39 -07003172 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003173
Tao Baoda30cfa2017-12-01 16:19:46 -08003174 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003175
3176
3177class DynamicPartitionUpdate(object):
3178 def __init__(self, src_group=None, tgt_group=None, progress=None,
3179 block_difference=None):
3180 self.src_group = src_group
3181 self.tgt_group = tgt_group
3182 self.progress = progress
3183 self.block_difference = block_difference
3184
3185 @property
3186 def src_size(self):
3187 if not self.block_difference:
3188 return 0
3189 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3190
3191 @property
3192 def tgt_size(self):
3193 if not self.block_difference:
3194 return 0
3195 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3196
3197 @staticmethod
3198 def _GetSparseImageSize(img):
3199 if not img:
3200 return 0
3201 return img.blocksize * img.total_blocks
3202
3203
3204class DynamicGroupUpdate(object):
3205 def __init__(self, src_size=None, tgt_size=None):
3206 # None: group does not exist. 0: no size limits.
3207 self.src_size = src_size
3208 self.tgt_size = tgt_size
3209
3210
3211class DynamicPartitionsDifference(object):
3212 def __init__(self, info_dict, block_diffs, progress_dict=None,
3213 source_info_dict=None):
3214 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003215 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003216
3217 self._remove_all_before_apply = False
3218 if source_info_dict is None:
3219 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003220 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003221
Tao Baof1113e92019-06-18 12:10:14 -07003222 block_diff_dict = collections.OrderedDict(
3223 [(e.partition, e) for e in block_diffs])
3224
Yifan Hong10c530d2018-12-27 17:34:18 -08003225 assert len(block_diff_dict) == len(block_diffs), \
3226 "Duplicated BlockDifference object for {}".format(
3227 [partition for partition, count in
3228 collections.Counter(e.partition for e in block_diffs).items()
3229 if count > 1])
3230
Yifan Hong79997e52019-01-23 16:56:19 -08003231 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003232
3233 for p, block_diff in block_diff_dict.items():
3234 self._partition_updates[p] = DynamicPartitionUpdate()
3235 self._partition_updates[p].block_difference = block_diff
3236
3237 for p, progress in progress_dict.items():
3238 if p in self._partition_updates:
3239 self._partition_updates[p].progress = progress
3240
3241 tgt_groups = shlex.split(info_dict.get(
3242 "super_partition_groups", "").strip())
3243 src_groups = shlex.split(source_info_dict.get(
3244 "super_partition_groups", "").strip())
3245
3246 for g in tgt_groups:
3247 for p in shlex.split(info_dict.get(
3248 "super_%s_partition_list" % g, "").strip()):
3249 assert p in self._partition_updates, \
3250 "{} is in target super_{}_partition_list but no BlockDifference " \
3251 "object is provided.".format(p, g)
3252 self._partition_updates[p].tgt_group = g
3253
3254 for g in src_groups:
3255 for p in shlex.split(source_info_dict.get(
3256 "super_%s_partition_list" % g, "").strip()):
3257 assert p in self._partition_updates, \
3258 "{} is in source super_{}_partition_list but no BlockDifference " \
3259 "object is provided.".format(p, g)
3260 self._partition_updates[p].src_group = g
3261
Yifan Hong45433e42019-01-18 13:55:25 -08003262 target_dynamic_partitions = set(shlex.split(info_dict.get(
3263 "dynamic_partition_list", "").strip()))
3264 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3265 if u.tgt_size)
3266 assert block_diffs_with_target == target_dynamic_partitions, \
3267 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3268 list(target_dynamic_partitions), list(block_diffs_with_target))
3269
3270 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3271 "dynamic_partition_list", "").strip()))
3272 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3273 if u.src_size)
3274 assert block_diffs_with_source == source_dynamic_partitions, \
3275 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3276 list(source_dynamic_partitions), list(block_diffs_with_source))
3277
Yifan Hong10c530d2018-12-27 17:34:18 -08003278 if self._partition_updates:
3279 logger.info("Updating dynamic partitions %s",
3280 self._partition_updates.keys())
3281
Yifan Hong79997e52019-01-23 16:56:19 -08003282 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003283
3284 for g in tgt_groups:
3285 self._group_updates[g] = DynamicGroupUpdate()
3286 self._group_updates[g].tgt_size = int(info_dict.get(
3287 "super_%s_group_size" % g, "0").strip())
3288
3289 for g in src_groups:
3290 if g not in self._group_updates:
3291 self._group_updates[g] = DynamicGroupUpdate()
3292 self._group_updates[g].src_size = int(source_info_dict.get(
3293 "super_%s_group_size" % g, "0").strip())
3294
3295 self._Compute()
3296
3297 def WriteScript(self, script, output_zip, write_verify_script=False):
3298 script.Comment('--- Start patching dynamic partitions ---')
3299 for p, u in self._partition_updates.items():
3300 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3301 script.Comment('Patch partition %s' % p)
3302 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3303 write_verify_script=False)
3304
3305 op_list_path = MakeTempFile()
3306 with open(op_list_path, 'w') as f:
3307 for line in self._op_list:
3308 f.write('{}\n'.format(line))
3309
3310 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3311
3312 script.Comment('Update dynamic partition metadata')
3313 script.AppendExtra('assert(update_dynamic_partitions('
3314 'package_extract_file("dynamic_partitions_op_list")));')
3315
3316 if write_verify_script:
3317 for p, u in self._partition_updates.items():
3318 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3319 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003320 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003321
3322 for p, u in self._partition_updates.items():
3323 if u.tgt_size and u.src_size <= u.tgt_size:
3324 script.Comment('Patch partition %s' % p)
3325 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3326 write_verify_script=write_verify_script)
3327 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003328 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003329
3330 script.Comment('--- End patching dynamic partitions ---')
3331
3332 def _Compute(self):
3333 self._op_list = list()
3334
3335 def append(line):
3336 self._op_list.append(line)
3337
3338 def comment(line):
3339 self._op_list.append("# %s" % line)
3340
3341 if self._remove_all_before_apply:
3342 comment('Remove all existing dynamic partitions and groups before '
3343 'applying full OTA')
3344 append('remove_all_groups')
3345
3346 for p, u in self._partition_updates.items():
3347 if u.src_group and not u.tgt_group:
3348 append('remove %s' % p)
3349
3350 for p, u in self._partition_updates.items():
3351 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3352 comment('Move partition %s from %s to default' % (p, u.src_group))
3353 append('move %s default' % p)
3354
3355 for p, u in self._partition_updates.items():
3356 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3357 comment('Shrink partition %s from %d to %d' %
3358 (p, u.src_size, u.tgt_size))
3359 append('resize %s %s' % (p, u.tgt_size))
3360
3361 for g, u in self._group_updates.items():
3362 if u.src_size is not None and u.tgt_size is None:
3363 append('remove_group %s' % g)
3364 if (u.src_size is not None and u.tgt_size is not None and
3365 u.src_size > u.tgt_size):
3366 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3367 append('resize_group %s %d' % (g, u.tgt_size))
3368
3369 for g, u in self._group_updates.items():
3370 if u.src_size is None and u.tgt_size is not None:
3371 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3372 append('add_group %s %d' % (g, u.tgt_size))
3373 if (u.src_size is not None and u.tgt_size is not None and
3374 u.src_size < u.tgt_size):
3375 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3376 append('resize_group %s %d' % (g, u.tgt_size))
3377
3378 for p, u in self._partition_updates.items():
3379 if u.tgt_group and not u.src_group:
3380 comment('Add partition %s to group %s' % (p, u.tgt_group))
3381 append('add %s %s' % (p, u.tgt_group))
3382
3383 for p, u in self._partition_updates.items():
3384 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003385 comment('Grow partition %s from %d to %d' %
3386 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003387 append('resize %s %d' % (p, u.tgt_size))
3388
3389 for p, u in self._partition_updates.items():
3390 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3391 comment('Move partition %s from default to %s' %
3392 (p, u.tgt_group))
3393 append('move %s %s' % (p, u.tgt_group))