blob: ee5cdc3bd1a76884bb88e7c98771acd0b11dbd60 [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',
Steve Mucklee1b10862019-07-10 10:49:37 -0700113 'system_ext', 'vendor', 'vendor_boot')
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
Justin Yun6151e3f2019-06-25 15:58:13 +0900119PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700120
121
Tianjie Xu209db462016-05-24 17:34:52 -0700122class ErrorCode(object):
123 """Define error_codes for failures that happen during the actual
124 update package installation.
125
126 Error codes 0-999 are reserved for failures before the package
127 installation (i.e. low battery, package verification failure).
128 Detailed code in 'bootable/recovery/error_code.h' """
129
130 SYSTEM_VERIFICATION_FAILURE = 1000
131 SYSTEM_UPDATE_FAILURE = 1001
132 SYSTEM_UNEXPECTED_CONTENTS = 1002
133 SYSTEM_NONZERO_CONTENTS = 1003
134 SYSTEM_RECOVER_FAILURE = 1004
135 VENDOR_VERIFICATION_FAILURE = 2000
136 VENDOR_UPDATE_FAILURE = 2001
137 VENDOR_UNEXPECTED_CONTENTS = 2002
138 VENDOR_NONZERO_CONTENTS = 2003
139 VENDOR_RECOVER_FAILURE = 2004
140 OEM_PROP_MISMATCH = 3000
141 FINGERPRINT_MISMATCH = 3001
142 THUMBPRINT_MISMATCH = 3002
143 OLDER_BUILD = 3003
144 DEVICE_MISMATCH = 3004
145 BAD_PATCH_FILE = 3005
146 INSUFFICIENT_CACHE_SPACE = 3006
147 TUNE_PARTITION_FAILURE = 3007
148 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800149
Tao Bao80921982018-03-21 21:02:19 -0700150
Dan Albert8b72aef2015-03-23 19:13:21 -0700151class ExternalError(RuntimeError):
152 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700153
154
Tao Bao32fcdab2018-10-12 10:30:39 -0700155def InitLogging():
156 DEFAULT_LOGGING_CONFIG = {
157 'version': 1,
158 'disable_existing_loggers': False,
159 'formatters': {
160 'standard': {
161 'format':
162 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
163 'datefmt': '%Y-%m-%d %H:%M:%S',
164 },
165 },
166 'handlers': {
167 'default': {
168 'class': 'logging.StreamHandler',
169 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700170 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700171 },
172 },
173 'loggers': {
174 '': {
175 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700176 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700177 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700178 }
179 }
180 }
181 env_config = os.getenv('LOGGING_CONFIG')
182 if env_config:
183 with open(env_config) as f:
184 config = json.load(f)
185 else:
186 config = DEFAULT_LOGGING_CONFIG
187
188 # Increase the logging level for verbose mode.
189 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700190 config = copy.deepcopy(config)
191 config['handlers']['default']['level'] = 'INFO'
192
193 if OPTIONS.logfile:
194 config = copy.deepcopy(config)
195 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400196 'class': 'logging.FileHandler',
197 'formatter': 'standard',
198 'level': 'INFO',
199 'mode': 'w',
200 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700201 }
202 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700203
204 logging.config.dictConfig(config)
205
206
Tao Bao39451582017-05-04 11:10:47 -0700207def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700208 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700209
Tao Bao73dd4f42018-10-04 16:25:33 -0700210 Args:
211 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700212 verbose: Whether the commands should be shown. Default to the global
213 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700214 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
215 stdin, etc. stdout and stderr will default to subprocess.PIPE and
216 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800217 universal_newlines will default to True, as most of the users in
218 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700219
220 Returns:
221 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700222 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700223 if 'stdout' not in kwargs and 'stderr' not in kwargs:
224 kwargs['stdout'] = subprocess.PIPE
225 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800226 if 'universal_newlines' not in kwargs:
227 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700228 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400229 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700230 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700231 return subprocess.Popen(args, **kwargs)
232
233
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800234def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800235 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800236
237 Args:
238 args: The command represented as a list of strings.
239 verbose: Whether the commands should be shown. Default to the global
240 verbosity if unspecified.
241 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
242 stdin, etc. stdout and stderr will default to subprocess.PIPE and
243 subprocess.STDOUT respectively unless caller specifies any of them.
244
Bill Peckham889b0c62019-02-21 18:53:37 -0800245 Raises:
246 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800247 """
248 proc = Run(args, verbose=verbose, **kwargs)
249 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800250
251 if proc.returncode != 0:
252 raise ExternalError(
253 "Failed to run command '{}' (exit code {})".format(
254 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800255
256
Tao Bao986ee862018-10-04 15:46:16 -0700257def RunAndCheckOutput(args, verbose=None, **kwargs):
258 """Runs the given command and returns the output.
259
260 Args:
261 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700262 verbose: Whether the commands should be shown. Default to the global
263 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700264 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
265 stdin, etc. stdout and stderr will default to subprocess.PIPE and
266 subprocess.STDOUT respectively unless caller specifies any of them.
267
268 Returns:
269 The output string.
270
271 Raises:
272 ExternalError: On non-zero exit from the command.
273 """
Tao Bao986ee862018-10-04 15:46:16 -0700274 proc = Run(args, verbose=verbose, **kwargs)
275 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800276 if output is None:
277 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700278 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400279 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700280 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700281 if proc.returncode != 0:
282 raise ExternalError(
283 "Failed to run command '{}' (exit code {}):\n{}".format(
284 args, proc.returncode, output))
285 return output
286
287
Tao Baoc765cca2018-01-31 17:32:40 -0800288def RoundUpTo4K(value):
289 rounded_up = value + 4095
290 return rounded_up - (rounded_up % 4096)
291
292
Ying Wang7e6d4e42010-12-13 16:25:36 -0800293def CloseInheritedPipes():
294 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
295 before doing other work."""
296 if platform.system() != "Darwin":
297 return
298 for d in range(3, 1025):
299 try:
300 stat = os.fstat(d)
301 if stat is not None:
302 pipebit = stat[0] & 0x1000
303 if pipebit != 0:
304 os.close(d)
305 except OSError:
306 pass
307
308
Tao Bao1c320f82019-10-04 23:25:12 -0700309class BuildInfo(object):
310 """A class that holds the information for a given build.
311
312 This class wraps up the property querying for a given source or target build.
313 It abstracts away the logic of handling OEM-specific properties, and caches
314 the commonly used properties such as fingerprint.
315
316 There are two types of info dicts: a) build-time info dict, which is generated
317 at build time (i.e. included in a target_files zip); b) OEM info dict that is
318 specified at package generation time (via command line argument
319 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
320 having "oem_fingerprint_properties" in build-time info dict), all the queries
321 would be answered based on build-time info dict only. Otherwise if using
322 OEM-specific properties, some of them will be calculated from two info dicts.
323
324 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800325 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700326
327 Attributes:
328 info_dict: The build-time info dict.
329 is_ab: Whether it's a build that uses A/B OTA.
330 oem_dicts: A list of OEM dicts.
331 oem_props: A list of OEM properties that should be read from OEM dicts; None
332 if the build doesn't use any OEM-specific property.
333 fingerprint: The fingerprint of the build, which would be calculated based
334 on OEM properties if applicable.
335 device: The device name, which could come from OEM dicts if applicable.
336 """
337
338 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
339 "ro.product.manufacturer", "ro.product.model",
340 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700341 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
342 "product", "odm", "vendor", "system_ext", "system"]
343 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
344 "product", "product_services", "odm", "vendor", "system"]
345 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700346
Tao Bao3ed35d32019-10-07 20:48:48 -0700347 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700348 """Initializes a BuildInfo instance with the given dicts.
349
350 Note that it only wraps up the given dicts, without making copies.
351
352 Arguments:
353 info_dict: The build-time info dict.
354 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
355 that it always uses the first dict to calculate the fingerprint or the
356 device name. The rest would be used for asserting OEM properties only
357 (e.g. one package can be installed on one of these devices).
358
359 Raises:
360 ValueError: On invalid inputs.
361 """
362 self.info_dict = info_dict
363 self.oem_dicts = oem_dicts
364
365 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700366
Hongguang Chend7c160f2020-05-03 21:24:26 -0700367 # Skip _oem_props if oem_dicts is None to use BuildInfo in
368 # sign_target_files_apks
369 if self.oem_dicts:
370 self._oem_props = info_dict.get("oem_fingerprint_properties")
371 else:
372 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700373
Daniel Normand5fe8622020-01-08 17:01:11 -0800374 def check_fingerprint(fingerprint):
375 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
376 raise ValueError(
377 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
378 "3.2.2. Build Parameters.".format(fingerprint))
379
Daniel Normand5fe8622020-01-08 17:01:11 -0800380 self._partition_fingerprints = {}
381 for partition in PARTITIONS_WITH_CARE_MAP:
382 try:
383 fingerprint = self.CalculatePartitionFingerprint(partition)
384 check_fingerprint(fingerprint)
385 self._partition_fingerprints[partition] = fingerprint
386 except ExternalError:
387 continue
388 if "system" in self._partition_fingerprints:
389 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
390 # need a fingerprint when creating the image.
391 self._partition_fingerprints[
392 "system_other"] = self._partition_fingerprints["system"]
393
Tao Bao1c320f82019-10-04 23:25:12 -0700394 # These two should be computed only after setting self._oem_props.
395 self._device = self.GetOemProperty("ro.product.device")
396 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800397 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700398
399 @property
400 def is_ab(self):
401 return self._is_ab
402
403 @property
404 def device(self):
405 return self._device
406
407 @property
408 def fingerprint(self):
409 return self._fingerprint
410
411 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700412 def oem_props(self):
413 return self._oem_props
414
415 def __getitem__(self, key):
416 return self.info_dict[key]
417
418 def __setitem__(self, key, value):
419 self.info_dict[key] = value
420
421 def get(self, key, default=None):
422 return self.info_dict.get(key, default)
423
424 def items(self):
425 return self.info_dict.items()
426
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000427 def _GetRawBuildProp(self, prop, partition):
428 prop_file = '{}.build.prop'.format(
429 partition) if partition else 'build.prop'
430 partition_props = self.info_dict.get(prop_file)
431 if not partition_props:
432 return None
433 return partition_props.GetProp(prop)
434
Daniel Normand5fe8622020-01-08 17:01:11 -0800435 def GetPartitionBuildProp(self, prop, partition):
436 """Returns the inquired build property for the provided partition."""
437 # If provided a partition for this property, only look within that
438 # partition's build.prop.
439 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
440 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
441 else:
442 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000443
444 prop_val = self._GetRawBuildProp(prop, partition)
445 if prop_val is not None:
446 return prop_val
447 raise ExternalError("couldn't find %s in %s.build.prop" %
448 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800449
Tao Bao1c320f82019-10-04 23:25:12 -0700450 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800451 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700452 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
453 return self._ResolveRoProductBuildProp(prop)
454
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000455 prop_val = self._GetRawBuildProp(prop, None)
456 if prop_val is not None:
457 return prop_val
458
459 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700460
461 def _ResolveRoProductBuildProp(self, prop):
462 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000463 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700464 if prop_val:
465 return prop_val
466
Steven Laver8e2086e2020-04-27 16:26:31 -0700467 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000468 source_order_val = self._GetRawBuildProp(
469 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700470 if source_order_val:
471 source_order = source_order_val.split(",")
472 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700473 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700474
475 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700476 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700477 raise ExternalError(
478 "Invalid ro.product.property_source_order '{}'".format(source_order))
479
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000480 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700481 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000482 "ro.product", "ro.product.{}".format(source_partition), 1)
483 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700484 if prop_val:
485 return prop_val
486
487 raise ExternalError("couldn't resolve {}".format(prop))
488
Steven Laver8e2086e2020-04-27 16:26:31 -0700489 def _GetRoProductPropsDefaultSourceOrder(self):
490 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
491 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000492 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700493 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000494 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700495 if android_version == "10":
496 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
497 # NOTE: float() conversion of android_version will have rounding error.
498 # We are checking for "9" or less, and using "< 10" is well outside of
499 # possible floating point rounding.
500 try:
501 android_version_val = float(android_version)
502 except ValueError:
503 android_version_val = 0
504 if android_version_val < 10:
505 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
506 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
507
Tao Bao1c320f82019-10-04 23:25:12 -0700508 def GetOemProperty(self, key):
509 if self.oem_props is not None and key in self.oem_props:
510 return self.oem_dicts[0][key]
511 return self.GetBuildProp(key)
512
Daniel Normand5fe8622020-01-08 17:01:11 -0800513 def GetPartitionFingerprint(self, partition):
514 return self._partition_fingerprints.get(partition, None)
515
516 def CalculatePartitionFingerprint(self, partition):
517 try:
518 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
519 except ExternalError:
520 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
521 self.GetPartitionBuildProp("ro.product.brand", partition),
522 self.GetPartitionBuildProp("ro.product.name", partition),
523 self.GetPartitionBuildProp("ro.product.device", partition),
524 self.GetPartitionBuildProp("ro.build.version.release", partition),
525 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400526 self.GetPartitionBuildProp(
527 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800528 self.GetPartitionBuildProp("ro.build.type", partition),
529 self.GetPartitionBuildProp("ro.build.tags", partition))
530
Tao Bao1c320f82019-10-04 23:25:12 -0700531 def CalculateFingerprint(self):
532 if self.oem_props is None:
533 try:
534 return self.GetBuildProp("ro.build.fingerprint")
535 except ExternalError:
536 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
537 self.GetBuildProp("ro.product.brand"),
538 self.GetBuildProp("ro.product.name"),
539 self.GetBuildProp("ro.product.device"),
540 self.GetBuildProp("ro.build.version.release"),
541 self.GetBuildProp("ro.build.id"),
542 self.GetBuildProp("ro.build.version.incremental"),
543 self.GetBuildProp("ro.build.type"),
544 self.GetBuildProp("ro.build.tags"))
545 return "%s/%s/%s:%s" % (
546 self.GetOemProperty("ro.product.brand"),
547 self.GetOemProperty("ro.product.name"),
548 self.GetOemProperty("ro.product.device"),
549 self.GetBuildProp("ro.build.thumbprint"))
550
551 def WriteMountOemScript(self, script):
552 assert self.oem_props is not None
553 recovery_mount_options = self.info_dict.get("recovery_mount_options")
554 script.Mount("/oem", recovery_mount_options)
555
556 def WriteDeviceAssertions(self, script, oem_no_mount):
557 # Read the property directly if not using OEM properties.
558 if not self.oem_props:
559 script.AssertDevice(self.device)
560 return
561
562 # Otherwise assert OEM properties.
563 if not self.oem_dicts:
564 raise ExternalError(
565 "No OEM file provided to answer expected assertions")
566
567 for prop in self.oem_props.split():
568 values = []
569 for oem_dict in self.oem_dicts:
570 if prop in oem_dict:
571 values.append(oem_dict[prop])
572 if not values:
573 raise ExternalError(
574 "The OEM file is missing the property %s" % (prop,))
575 script.AssertOemProperty(prop, values, oem_no_mount)
576
577
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000578def ReadFromInputFile(input_file, fn):
579 """Reads the contents of fn from input zipfile or directory."""
580 if isinstance(input_file, zipfile.ZipFile):
581 return input_file.read(fn).decode()
582 else:
583 path = os.path.join(input_file, *fn.split("/"))
584 try:
585 with open(path) as f:
586 return f.read()
587 except IOError as e:
588 if e.errno == errno.ENOENT:
589 raise KeyError(fn)
590
591
Tao Bao410ad8b2018-08-24 12:08:38 -0700592def LoadInfoDict(input_file, repacking=False):
593 """Loads the key/value pairs from the given input target_files.
594
595 It reads `META/misc_info.txt` file in the target_files input, does sanity
596 checks and returns the parsed key/value pairs for to the given build. It's
597 usually called early when working on input target_files files, e.g. when
598 generating OTAs, or signing builds. Note that the function may be called
599 against an old target_files file (i.e. from past dessert releases). So the
600 property parsing needs to be backward compatible.
601
602 In a `META/misc_info.txt`, a few properties are stored as links to the files
603 in the PRODUCT_OUT directory. It works fine with the build system. However,
604 they are no longer available when (re)generating images from target_files zip.
605 When `repacking` is True, redirect these properties to the actual files in the
606 unzipped directory.
607
608 Args:
609 input_file: The input target_files file, which could be an open
610 zipfile.ZipFile instance, or a str for the dir that contains the files
611 unzipped from a target_files file.
612 repacking: Whether it's trying repack an target_files file after loading the
613 info dict (default: False). If so, it will rewrite a few loaded
614 properties (e.g. selinux_fc, root_dir) to point to the actual files in
615 target_files file. When doing repacking, `input_file` must be a dir.
616
617 Returns:
618 A dict that contains the parsed key/value pairs.
619
620 Raises:
621 AssertionError: On invalid input arguments.
622 ValueError: On malformed input values.
623 """
624 if repacking:
625 assert isinstance(input_file, str), \
626 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700627
Doug Zongkerc9253822014-02-04 12:17:58 -0800628 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000629 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800630
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700631 try:
Michael Runge6e836112014-04-15 17:40:21 -0700632 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700633 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700634 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700635
Tao Bao410ad8b2018-08-24 12:08:38 -0700636 if "recovery_api_version" not in d:
637 raise ValueError("Failed to find 'recovery_api_version'")
638 if "fstab_version" not in d:
639 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800640
Tao Bao410ad8b2018-08-24 12:08:38 -0700641 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700642 # "selinux_fc" properties should point to the file_contexts files
643 # (file_contexts.bin) under META/.
644 for key in d:
645 if key.endswith("selinux_fc"):
646 fc_basename = os.path.basename(d[key])
647 fc_config = os.path.join(input_file, "META", fc_basename)
648 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700649
Daniel Norman72c626f2019-05-13 15:58:14 -0700650 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700651
Tom Cherryd14b8952018-08-09 14:26:00 -0700652 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700653 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700654 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700655 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700656
David Anderson0ec64ac2019-12-06 12:21:18 -0800657 # Redirect {partition}_base_fs_file for each of the named partitions.
658 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
659 key_name = part_name + "_base_fs_file"
660 if key_name not in d:
661 continue
662 basename = os.path.basename(d[key_name])
663 base_fs_file = os.path.join(input_file, "META", basename)
664 if os.path.exists(base_fs_file):
665 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700666 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700667 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800668 "Failed to find %s base fs file: %s", part_name, base_fs_file)
669 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700670
Doug Zongker37974732010-09-16 17:44:38 -0700671 def makeint(key):
672 if key in d:
673 d[key] = int(d[key], 0)
674
675 makeint("recovery_api_version")
676 makeint("blocksize")
677 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700678 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700679 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700680 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700681 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800682 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700683
Steve Muckle903a1ca2020-05-07 17:32:10 -0700684 boot_images = "boot.img"
685 if "boot_images" in d:
686 boot_images = d["boot_images"]
687 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400688 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700689
Tao Bao765668f2019-10-04 22:03:00 -0700690 # Load recovery fstab if applicable.
691 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800692
Tianjie Xu861f4132018-09-12 11:49:33 -0700693 # Tries to load the build props for all partitions with care_map, including
694 # system and vendor.
695 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800696 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000697 d[partition_prop] = PartitionBuildProps.FromInputFile(
698 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700699 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800700
Tao Bao3ed35d32019-10-07 20:48:48 -0700701 # Set up the salt (based on fingerprint) that will be used when adding AVB
702 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800703 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700704 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800705 for partition in PARTITIONS_WITH_CARE_MAP:
706 fingerprint = build_info.GetPartitionFingerprint(partition)
707 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400708 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800709
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700710 return d
711
Tao Baod1de6f32017-03-01 16:38:48 -0800712
Daniel Norman4cc9df62019-07-18 10:11:07 -0700713def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900714 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700715 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900716
Daniel Norman4cc9df62019-07-18 10:11:07 -0700717
718def LoadDictionaryFromFile(file_path):
719 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900720 return LoadDictionaryFromLines(lines)
721
722
Michael Runge6e836112014-04-15 17:40:21 -0700723def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700724 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700725 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700726 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700727 if not line or line.startswith("#"):
728 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700729 if "=" in line:
730 name, value = line.split("=", 1)
731 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700732 return d
733
Tao Baod1de6f32017-03-01 16:38:48 -0800734
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000735class PartitionBuildProps(object):
736 """The class holds the build prop of a particular partition.
737
738 This class loads the build.prop and holds the build properties for a given
739 partition. It also partially recognizes the 'import' statement in the
740 build.prop; and calculates alternative values of some specific build
741 properties during runtime.
742
743 Attributes:
744 input_file: a zipped target-file or an unzipped target-file directory.
745 partition: name of the partition.
746 props_allow_override: a list of build properties to search for the
747 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000748 build_props: a dict of build properties for the given partition.
749 prop_overrides: a set of props that are overridden by import.
750 placeholder_values: A dict of runtime variables' values to replace the
751 placeholders in the build.prop file. We expect exactly one value for
752 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000753 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400754
Tianjie Xu9afb2212020-05-10 21:48:15 +0000755 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000756 self.input_file = input_file
757 self.partition = name
758 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000759 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000760 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000761 self.prop_overrides = set()
762 self.placeholder_values = {}
763 if placeholder_values:
764 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000765
766 @staticmethod
767 def FromDictionary(name, build_props):
768 """Constructs an instance from a build prop dictionary."""
769
770 props = PartitionBuildProps("unknown", name)
771 props.build_props = build_props.copy()
772 return props
773
774 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000775 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000776 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000777 data = ''
778 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
779 '{}/build.prop'.format(name.upper())]:
780 try:
781 data = ReadFromInputFile(input_file, prop_file)
782 break
783 except KeyError:
784 logger.warning('Failed to read %s', prop_file)
785
Tianjie Xu9afb2212020-05-10 21:48:15 +0000786 props = PartitionBuildProps(input_file, name, placeholder_values)
787 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000788 return props
789
Tianjie Xu9afb2212020-05-10 21:48:15 +0000790 def _LoadBuildProp(self, data):
791 for line in data.split('\n'):
792 line = line.strip()
793 if not line or line.startswith("#"):
794 continue
795 if line.startswith("import"):
796 overrides = self._ImportParser(line)
797 duplicates = self.prop_overrides.intersection(overrides.keys())
798 if duplicates:
799 raise ValueError('prop {} is overridden multiple times'.format(
800 ','.join(duplicates)))
801 self.prop_overrides = self.prop_overrides.union(overrides.keys())
802 self.build_props.update(overrides)
803 elif "=" in line:
804 name, value = line.split("=", 1)
805 if name in self.prop_overrides:
806 raise ValueError('prop {} is set again after overridden by import '
807 'statement'.format(name))
808 self.build_props[name] = value
809
810 def _ImportParser(self, line):
811 """Parses the build prop in a given import statement."""
812
813 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400814 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000815 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700816
817 if len(tokens) == 3:
818 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
819 return {}
820
Tianjie Xu9afb2212020-05-10 21:48:15 +0000821 import_path = tokens[1]
822 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
823 raise ValueError('Unrecognized import path {}'.format(line))
824
825 # We only recognize a subset of import statement that the init process
826 # supports. And we can loose the restriction based on how the dynamic
827 # fingerprint is used in practice. The placeholder format should be
828 # ${placeholder}, and its value should be provided by the caller through
829 # the placeholder_values.
830 for prop, value in self.placeholder_values.items():
831 prop_place_holder = '${{{}}}'.format(prop)
832 if prop_place_holder in import_path:
833 import_path = import_path.replace(prop_place_holder, value)
834 if '$' in import_path:
835 logger.info('Unresolved place holder in import path %s', import_path)
836 return {}
837
838 import_path = import_path.replace('/{}'.format(self.partition),
839 self.partition.upper())
840 logger.info('Parsing build props override from %s', import_path)
841
842 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
843 d = LoadDictionaryFromLines(lines)
844 return {key: val for key, val in d.items()
845 if key in self.props_allow_override}
846
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000847 def GetProp(self, prop):
848 return self.build_props.get(prop)
849
850
Tianjie Xucfa86222016-03-07 16:31:19 -0800851def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
852 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700853 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700854 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700855 self.mount_point = mount_point
856 self.fs_type = fs_type
857 self.device = device
858 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700859 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700860 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700861
862 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800863 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700864 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700865 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700866 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700867
Tao Baod1de6f32017-03-01 16:38:48 -0800868 assert fstab_version == 2
869
870 d = {}
871 for line in data.split("\n"):
872 line = line.strip()
873 if not line or line.startswith("#"):
874 continue
875
876 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
877 pieces = line.split()
878 if len(pieces) != 5:
879 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
880
881 # Ignore entries that are managed by vold.
882 options = pieces[4]
883 if "voldmanaged=" in options:
884 continue
885
886 # It's a good line, parse it.
887 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700888 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800889 options = options.split(",")
890 for i in options:
891 if i.startswith("length="):
892 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700893 elif i == "slotselect":
894 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800895 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800896 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700897 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800898
Tao Baod1de6f32017-03-01 16:38:48 -0800899 mount_flags = pieces[3]
900 # Honor the SELinux context if present.
901 context = None
902 for i in mount_flags.split(","):
903 if i.startswith("context="):
904 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800905
Tao Baod1de6f32017-03-01 16:38:48 -0800906 mount_point = pieces[1]
907 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700908 device=pieces[0], length=length, context=context,
909 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800910
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700911 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700912 # system. Other areas assume system is always at "/system" so point /system
913 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700914 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800915 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700916 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700917 return d
918
919
Tao Bao765668f2019-10-04 22:03:00 -0700920def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
921 """Finds the path to recovery fstab and loads its contents."""
922 # recovery fstab is only meaningful when installing an update via recovery
923 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -0700924 if info_dict.get('ab_update') == 'true' and \
925 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -0700926 return None
927
928 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
929 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
930 # cases, since it may load the info_dict from an old build (e.g. when
931 # generating incremental OTAs from that build).
932 system_root_image = info_dict.get('system_root_image') == 'true'
933 if info_dict.get('no_recovery') != 'true':
934 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
935 if isinstance(input_file, zipfile.ZipFile):
936 if recovery_fstab_path not in input_file.namelist():
937 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
938 else:
939 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
940 if not os.path.exists(path):
941 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
942 return LoadRecoveryFSTab(
943 read_helper, info_dict['fstab_version'], recovery_fstab_path,
944 system_root_image)
945
946 if info_dict.get('recovery_as_boot') == 'true':
947 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
948 if isinstance(input_file, zipfile.ZipFile):
949 if recovery_fstab_path not in input_file.namelist():
950 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
951 else:
952 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
953 if not os.path.exists(path):
954 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
955 return LoadRecoveryFSTab(
956 read_helper, info_dict['fstab_version'], recovery_fstab_path,
957 system_root_image)
958
959 return None
960
961
Doug Zongker37974732010-09-16 17:44:38 -0700962def DumpInfoDict(d):
963 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700964 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700965
Dan Albert8b72aef2015-03-23 19:13:21 -0700966
Daniel Norman55417142019-11-25 16:04:36 -0800967def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700968 """Merges dynamic partition info variables.
969
970 Args:
971 framework_dict: The dictionary of dynamic partition info variables from the
972 partial framework target files.
973 vendor_dict: The dictionary of dynamic partition info variables from the
974 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700975
976 Returns:
977 The merged dynamic partition info dictionary.
978 """
979 merged_dict = {}
980 # Partition groups and group sizes are defined by the vendor dict because
981 # these values may vary for each board that uses a shared system image.
982 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800983 framework_dynamic_partition_list = framework_dict.get(
984 "dynamic_partition_list", "")
985 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
986 merged_dict["dynamic_partition_list"] = ("%s %s" % (
987 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700988 for partition_group in merged_dict["super_partition_groups"].split(" "):
989 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800990 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700991 if key not in vendor_dict:
992 raise ValueError("Vendor dict does not contain required key %s." % key)
993 merged_dict[key] = vendor_dict[key]
994
995 # Set the partition group's partition list using a concatenation of the
996 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800997 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700998 merged_dict[key] = (
999 "%s %s" %
1000 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301001
1002 # Pick virtual ab related flags from vendor dict, if defined.
1003 if "virtual_ab" in vendor_dict.keys():
Kelvin Zhang0876c412020-06-23 15:06:58 -04001004 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301005 if "virtual_ab_retrofit" in vendor_dict.keys():
Kelvin Zhang0876c412020-06-23 15:06:58 -04001006 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001007 return merged_dict
1008
1009
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001010def AppendAVBSigningArgs(cmd, partition):
1011 """Append signing arguments for avbtool."""
1012 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1013 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001014 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1015 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1016 if os.path.exists(new_key_path):
1017 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001018 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1019 if key_path and algorithm:
1020 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001021 avb_salt = OPTIONS.info_dict.get("avb_salt")
1022 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001023 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001024 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001025
1026
Tao Bao765668f2019-10-04 22:03:00 -07001027def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001028 """Returns the VBMeta arguments for partition.
1029
1030 It sets up the VBMeta argument by including the partition descriptor from the
1031 given 'image', or by configuring the partition as a chained partition.
1032
1033 Args:
1034 partition: The name of the partition (e.g. "system").
1035 image: The path to the partition image.
1036 info_dict: A dict returned by common.LoadInfoDict(). Will use
1037 OPTIONS.info_dict if None has been given.
1038
1039 Returns:
1040 A list of VBMeta arguments.
1041 """
1042 if info_dict is None:
1043 info_dict = OPTIONS.info_dict
1044
1045 # Check if chain partition is used.
1046 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001047 if not key_path:
1048 return ["--include_descriptors_from_image", image]
1049
1050 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1051 # into vbmeta.img. The recovery image will be configured on an independent
1052 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1053 # See details at
1054 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001055 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001056 return []
1057
1058 # Otherwise chain the partition into vbmeta.
1059 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1060 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001061
1062
Tao Bao02a08592018-07-22 12:40:45 -07001063def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1064 """Constructs and returns the arg to build or verify a chained partition.
1065
1066 Args:
1067 partition: The partition name.
1068 info_dict: The info dict to look up the key info and rollback index
1069 location.
1070 key: The key to be used for building or verifying the partition. Defaults to
1071 the key listed in info_dict.
1072
1073 Returns:
1074 A string of form "partition:rollback_index_location:key" that can be used to
1075 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001076 """
1077 if key is None:
1078 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001079 if key and not os.path.exists(key) and OPTIONS.search_path:
1080 new_key_path = os.path.join(OPTIONS.search_path, key)
1081 if os.path.exists(new_key_path):
1082 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001083 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001084 rollback_index_location = info_dict[
1085 "avb_" + partition + "_rollback_index_location"]
1086 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1087
1088
Tianjie20dd8f22020-04-19 15:51:16 -07001089def ConstructAftlMakeImageCommands(output_image):
1090 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001091
1092 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001093 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001094 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1095 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1096 'No AFTL manufacturer key provided.'
1097
1098 vbmeta_image = MakeTempFile()
1099 os.rename(output_image, vbmeta_image)
1100 build_info = BuildInfo(OPTIONS.info_dict)
1101 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001102 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001103 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001104 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001105 "--vbmeta_image_path", vbmeta_image,
1106 "--output", output_image,
1107 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001108 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001109 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1110 "--algorithm", "SHA256_RSA4096",
1111 "--padding", "4096"]
1112 if OPTIONS.aftl_signer_helper:
1113 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001114 return aftl_cmd
1115
1116
1117def AddAftlInclusionProof(output_image):
1118 """Appends the aftl inclusion proof to the vbmeta image."""
1119
1120 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001121 RunAndCheckOutput(aftl_cmd)
1122
1123 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1124 output_image, '--transparency_log_pub_keys',
1125 OPTIONS.aftl_key_path]
1126 RunAndCheckOutput(verify_cmd)
1127
1128
Daniel Norman276f0622019-07-26 14:13:51 -07001129def BuildVBMeta(image_path, partitions, name, needed_partitions):
1130 """Creates a VBMeta image.
1131
1132 It generates the requested VBMeta image. The requested image could be for
1133 top-level or chained VBMeta image, which is determined based on the name.
1134
1135 Args:
1136 image_path: The output path for the new VBMeta image.
1137 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001138 values. Only valid partition names are accepted, as partitions listed
1139 in common.AVB_PARTITIONS and custom partitions listed in
1140 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001141 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1142 needed_partitions: Partitions whose descriptors should be included into the
1143 generated VBMeta image.
1144
1145 Raises:
1146 AssertionError: On invalid input args.
1147 """
1148 avbtool = OPTIONS.info_dict["avb_avbtool"]
1149 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1150 AppendAVBSigningArgs(cmd, name)
1151
Hongguang Chenf23364d2020-04-27 18:36:36 -07001152 custom_partitions = OPTIONS.info_dict.get(
1153 "avb_custom_images_partition_list", "").strip().split()
1154
Daniel Norman276f0622019-07-26 14:13:51 -07001155 for partition, path in partitions.items():
1156 if partition not in needed_partitions:
1157 continue
1158 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001159 partition in AVB_VBMETA_PARTITIONS or
1160 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001161 'Unknown partition: {}'.format(partition)
1162 assert os.path.exists(path), \
1163 'Failed to find {} for {}'.format(path, partition)
1164 cmd.extend(GetAvbPartitionArg(partition, path))
1165
1166 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1167 if args and args.strip():
1168 split_args = shlex.split(args)
1169 for index, arg in enumerate(split_args[:-1]):
1170 # Sanity check that the image file exists. Some images might be defined
1171 # as a path relative to source tree, which may not be available at the
1172 # same location when running this script (we have the input target_files
1173 # zip only). For such cases, we additionally scan other locations (e.g.
1174 # IMAGES/, RADIO/, etc) before bailing out.
1175 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001176 chained_image = split_args[index + 1]
1177 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001178 continue
1179 found = False
1180 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1181 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001182 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001183 if os.path.exists(alt_path):
1184 split_args[index + 1] = alt_path
1185 found = True
1186 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001187 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001188 cmd.extend(split_args)
1189
1190 RunAndCheckOutput(cmd)
1191
Tianjie Xueaed60c2020-03-12 00:33:28 -07001192 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001193 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001194 AddAftlInclusionProof(image_path)
1195
Daniel Norman276f0622019-07-26 14:13:51 -07001196
Steve Mucklee1b10862019-07-10 10:49:37 -07001197def _MakeRamdisk(sourcedir, fs_config_file=None):
1198 ramdisk_img = tempfile.NamedTemporaryFile()
1199
1200 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1201 cmd = ["mkbootfs", "-f", fs_config_file,
1202 os.path.join(sourcedir, "RAMDISK")]
1203 else:
1204 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1205 p1 = Run(cmd, stdout=subprocess.PIPE)
1206 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1207
1208 p2.wait()
1209 p1.wait()
1210 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1211 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1212
1213 return ramdisk_img
1214
1215
Steve Muckle9793cf62020-04-08 18:27:00 -07001216def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001217 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001218 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001219
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001220 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001221 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1222 we are building a two-step special image (i.e. building a recovery image to
1223 be loaded into /boot in two-step OTAs).
1224
1225 Return the image data, or None if sourcedir does not appear to contains files
1226 for building the requested image.
1227 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001228
Steve Muckle9793cf62020-04-08 18:27:00 -07001229 # "boot" or "recovery", without extension.
1230 partition_name = os.path.basename(sourcedir).lower()
1231
1232 if partition_name == "recovery":
1233 kernel = "kernel"
1234 else:
1235 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001236 kernel = kernel.replace(".img", "")
Steve Muckle9793cf62020-04-08 18:27:00 -07001237 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001238 return None
1239
1240 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001241 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001242
Doug Zongkerd5131602012-08-02 14:46:42 -07001243 if info_dict is None:
1244 info_dict = OPTIONS.info_dict
1245
Doug Zongkereef39442009-04-02 12:14:19 -07001246 img = tempfile.NamedTemporaryFile()
1247
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001248 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001249 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001250
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001251 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1252 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1253
Steve Muckle9793cf62020-04-08 18:27:00 -07001254 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001255
Benoit Fradina45a8682014-07-14 21:00:43 +02001256 fn = os.path.join(sourcedir, "second")
1257 if os.access(fn, os.F_OK):
1258 cmd.append("--second")
1259 cmd.append(fn)
1260
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001261 fn = os.path.join(sourcedir, "dtb")
1262 if os.access(fn, os.F_OK):
1263 cmd.append("--dtb")
1264 cmd.append(fn)
1265
Doug Zongker171f1cd2009-06-15 22:36:37 -07001266 fn = os.path.join(sourcedir, "cmdline")
1267 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001268 cmd.append("--cmdline")
1269 cmd.append(open(fn).read().rstrip("\n"))
1270
1271 fn = os.path.join(sourcedir, "base")
1272 if os.access(fn, os.F_OK):
1273 cmd.append("--base")
1274 cmd.append(open(fn).read().rstrip("\n"))
1275
Ying Wang4de6b5b2010-08-25 14:29:34 -07001276 fn = os.path.join(sourcedir, "pagesize")
1277 if os.access(fn, os.F_OK):
1278 cmd.append("--pagesize")
1279 cmd.append(open(fn).read().rstrip("\n"))
1280
Steve Mucklef84668e2020-03-16 19:13:46 -07001281 if partition_name == "recovery":
1282 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301283 if not args:
1284 # Fall back to "mkbootimg_args" for recovery image
1285 # in case "recovery_mkbootimg_args" is not set.
1286 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001287 else:
1288 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001289 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001290 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001291
Tao Bao76def242017-11-21 09:25:31 -08001292 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001293 if args and args.strip():
1294 cmd.extend(shlex.split(args))
1295
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001296 if has_ramdisk:
1297 cmd.extend(["--ramdisk", ramdisk_img.name])
1298
Tao Baod95e9fd2015-03-29 23:07:41 -07001299 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001300 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001301 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001302 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001303 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001304 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001305
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001306 if partition_name == "recovery":
1307 if info_dict.get("include_recovery_dtbo") == "true":
1308 fn = os.path.join(sourcedir, "recovery_dtbo")
1309 cmd.extend(["--recovery_dtbo", fn])
1310 if info_dict.get("include_recovery_acpio") == "true":
1311 fn = os.path.join(sourcedir, "recovery_acpio")
1312 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001313
Tao Bao986ee862018-10-04 15:46:16 -07001314 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001315
Tao Bao76def242017-11-21 09:25:31 -08001316 if (info_dict.get("boot_signer") == "true" and
1317 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001318 # Hard-code the path as "/boot" for two-step special recovery image (which
1319 # will be loaded into /boot during the two-step OTA).
1320 if two_step_image:
1321 path = "/boot"
1322 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001323 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001324 cmd = [OPTIONS.boot_signer_path]
1325 cmd.extend(OPTIONS.boot_signer_args)
1326 cmd.extend([path, img.name,
1327 info_dict["verity_key"] + ".pk8",
1328 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001329 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001330
Tao Baod95e9fd2015-03-29 23:07:41 -07001331 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001332 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001333 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001334 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001335 # We have switched from the prebuilt futility binary to using the tool
1336 # (futility-host) built from the source. Override the setting in the old
1337 # TF.zip.
1338 futility = info_dict["futility"]
1339 if futility.startswith("prebuilts/"):
1340 futility = "futility-host"
1341 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001342 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001343 info_dict["vboot_key"] + ".vbprivk",
1344 info_dict["vboot_subkey"] + ".vbprivk",
1345 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001346 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001347 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001348
Tao Baof3282b42015-04-01 11:21:55 -07001349 # Clean up the temp files.
1350 img_unsigned.close()
1351 img_keyblock.close()
1352
David Zeuthen8fecb282017-12-01 16:24:01 -05001353 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001354 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001355 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001356 if partition_name == "recovery":
1357 part_size = info_dict["recovery_size"]
1358 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001359 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001360 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001361 "--partition_size", str(part_size), "--partition_name",
1362 partition_name]
1363 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001364 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001365 if args and args.strip():
1366 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001367 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001368
1369 img.seek(os.SEEK_SET, 0)
1370 data = img.read()
1371
1372 if has_ramdisk:
1373 ramdisk_img.close()
1374 img.close()
1375
1376 return data
1377
1378
Doug Zongkerd5131602012-08-02 14:46:42 -07001379def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001380 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001381 """Return a File object with the desired bootable image.
1382
1383 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1384 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1385 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001386
Doug Zongker55d93282011-01-25 17:03:34 -08001387 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1388 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001389 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001390 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001391
1392 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1393 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001394 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001395 return File.FromLocalFile(name, prebuilt_path)
1396
Tao Bao32fcdab2018-10-12 10:30:39 -07001397 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001398
1399 if info_dict is None:
1400 info_dict = OPTIONS.info_dict
1401
1402 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001403 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1404 # for recovery.
1405 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1406 prebuilt_name != "boot.img" or
1407 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001408
Doug Zongker6f1d0312014-08-22 08:07:12 -07001409 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001410 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001411 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001412 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001413 if data:
1414 return File(name, data)
1415 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001416
Doug Zongkereef39442009-04-02 12:14:19 -07001417
Steve Mucklee1b10862019-07-10 10:49:37 -07001418def _BuildVendorBootImage(sourcedir, info_dict=None):
1419 """Build a vendor boot image from the specified sourcedir.
1420
1421 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1422 turn them into a vendor boot image.
1423
1424 Return the image data, or None if sourcedir does not appear to contains files
1425 for building the requested image.
1426 """
1427
1428 if info_dict is None:
1429 info_dict = OPTIONS.info_dict
1430
1431 img = tempfile.NamedTemporaryFile()
1432
1433 ramdisk_img = _MakeRamdisk(sourcedir)
1434
1435 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1436 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1437
1438 cmd = [mkbootimg]
1439
1440 fn = os.path.join(sourcedir, "dtb")
1441 if os.access(fn, os.F_OK):
1442 cmd.append("--dtb")
1443 cmd.append(fn)
1444
1445 fn = os.path.join(sourcedir, "vendor_cmdline")
1446 if os.access(fn, os.F_OK):
1447 cmd.append("--vendor_cmdline")
1448 cmd.append(open(fn).read().rstrip("\n"))
1449
1450 fn = os.path.join(sourcedir, "base")
1451 if os.access(fn, os.F_OK):
1452 cmd.append("--base")
1453 cmd.append(open(fn).read().rstrip("\n"))
1454
1455 fn = os.path.join(sourcedir, "pagesize")
1456 if os.access(fn, os.F_OK):
1457 cmd.append("--pagesize")
1458 cmd.append(open(fn).read().rstrip("\n"))
1459
1460 args = info_dict.get("mkbootimg_args")
1461 if args and args.strip():
1462 cmd.extend(shlex.split(args))
1463
1464 args = info_dict.get("mkbootimg_version_args")
1465 if args and args.strip():
1466 cmd.extend(shlex.split(args))
1467
1468 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1469 cmd.extend(["--vendor_boot", img.name])
1470
1471 RunAndCheckOutput(cmd)
1472
1473 # AVB: if enabled, calculate and add hash.
1474 if info_dict.get("avb_enable") == "true":
1475 avbtool = info_dict["avb_avbtool"]
1476 part_size = info_dict["vendor_boot_size"]
1477 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001478 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001479 AppendAVBSigningArgs(cmd, "vendor_boot")
1480 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1481 if args and args.strip():
1482 cmd.extend(shlex.split(args))
1483 RunAndCheckOutput(cmd)
1484
1485 img.seek(os.SEEK_SET, 0)
1486 data = img.read()
1487
1488 ramdisk_img.close()
1489 img.close()
1490
1491 return data
1492
1493
1494def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1495 info_dict=None):
1496 """Return a File object with the desired vendor boot image.
1497
1498 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1499 the source files in 'unpack_dir'/'tree_subdir'."""
1500
1501 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1502 if os.path.exists(prebuilt_path):
1503 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1504 return File.FromLocalFile(name, prebuilt_path)
1505
1506 logger.info("building image from target_files %s...", tree_subdir)
1507
1508 if info_dict is None:
1509 info_dict = OPTIONS.info_dict
1510
Kelvin Zhang0876c412020-06-23 15:06:58 -04001511 data = _BuildVendorBootImage(
1512 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001513 if data:
1514 return File(name, data)
1515 return None
1516
1517
Narayan Kamatha07bf042017-08-14 14:49:21 +01001518def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001519 """Gunzips the given gzip compressed file to a given output file."""
1520 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001521 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001522 shutil.copyfileobj(in_file, out_file)
1523
1524
Tao Bao0ff15de2019-03-20 11:26:06 -07001525def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001526 """Unzips the archive to the given directory.
1527
1528 Args:
1529 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001530 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001531 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1532 archvie. Non-matching patterns will be filtered out. If there's no match
1533 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001534 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001535 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001536 if patterns is not None:
1537 # Filter out non-matching patterns. unzip will complain otherwise.
1538 with zipfile.ZipFile(filename) as input_zip:
1539 names = input_zip.namelist()
1540 filtered = [
1541 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1542
1543 # There isn't any matching files. Don't unzip anything.
1544 if not filtered:
1545 return
1546 cmd.extend(filtered)
1547
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001548 RunAndCheckOutput(cmd)
1549
1550
Doug Zongker75f17362009-12-08 13:46:44 -08001551def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001552 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001553
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001554 Args:
1555 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1556 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1557
1558 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1559 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001560
Tao Bao1c830bf2017-12-25 10:43:47 -08001561 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001562 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001563 """
Doug Zongkereef39442009-04-02 12:14:19 -07001564
Tao Bao1c830bf2017-12-25 10:43:47 -08001565 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001566 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1567 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001568 UnzipToDir(m.group(1), tmp, pattern)
1569 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001570 filename = m.group(1)
1571 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001572 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001573
Tao Baodba59ee2018-01-09 13:21:02 -08001574 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001575
1576
Yifan Hong8a66a712019-04-04 15:37:57 -07001577def GetUserImage(which, tmpdir, input_zip,
1578 info_dict=None,
1579 allow_shared_blocks=None,
1580 hashtree_info_generator=None,
1581 reset_file_map=False):
1582 """Returns an Image object suitable for passing to BlockImageDiff.
1583
1584 This function loads the specified image from the given path. If the specified
1585 image is sparse, it also performs additional processing for OTA purpose. For
1586 example, it always adds block 0 to clobbered blocks list. It also detects
1587 files that cannot be reconstructed from the block list, for whom we should
1588 avoid applying imgdiff.
1589
1590 Args:
1591 which: The partition name.
1592 tmpdir: The directory that contains the prebuilt image and block map file.
1593 input_zip: The target-files ZIP archive.
1594 info_dict: The dict to be looked up for relevant info.
1595 allow_shared_blocks: If image is sparse, whether having shared blocks is
1596 allowed. If none, it is looked up from info_dict.
1597 hashtree_info_generator: If present and image is sparse, generates the
1598 hashtree_info for this sparse image.
1599 reset_file_map: If true and image is sparse, reset file map before returning
1600 the image.
1601 Returns:
1602 A Image object. If it is a sparse image and reset_file_map is False, the
1603 image will have file_map info loaded.
1604 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001605 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001606 info_dict = LoadInfoDict(input_zip)
1607
1608 is_sparse = info_dict.get("extfs_sparse_flag")
1609
1610 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1611 # shared blocks (i.e. some blocks will show up in multiple files' block
1612 # list). We can only allocate such shared blocks to the first "owner", and
1613 # disable imgdiff for all later occurrences.
1614 if allow_shared_blocks is None:
1615 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1616
1617 if is_sparse:
1618 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1619 hashtree_info_generator)
1620 if reset_file_map:
1621 img.ResetFileMap()
1622 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001623 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001624
1625
1626def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1627 """Returns a Image object suitable for passing to BlockImageDiff.
1628
1629 This function loads the specified non-sparse image from the given path.
1630
1631 Args:
1632 which: The partition name.
1633 tmpdir: The directory that contains the prebuilt image and block map file.
1634 Returns:
1635 A Image object.
1636 """
1637 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1638 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1639
1640 # The image and map files must have been created prior to calling
1641 # ota_from_target_files.py (since LMP).
1642 assert os.path.exists(path) and os.path.exists(mappath)
1643
Tianjie Xu41976c72019-07-03 13:57:01 -07001644 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1645
Yifan Hong8a66a712019-04-04 15:37:57 -07001646
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001647def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1648 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001649 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1650
1651 This function loads the specified sparse image from the given path, and
1652 performs additional processing for OTA purpose. For example, it always adds
1653 block 0 to clobbered blocks list. It also detects files that cannot be
1654 reconstructed from the block list, for whom we should avoid applying imgdiff.
1655
1656 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001657 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001658 tmpdir: The directory that contains the prebuilt image and block map file.
1659 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001660 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001661 hashtree_info_generator: If present, generates the hashtree_info for this
1662 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001663 Returns:
1664 A SparseImage object, with file_map info loaded.
1665 """
Tao Baoc765cca2018-01-31 17:32:40 -08001666 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1667 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1668
1669 # The image and map files must have been created prior to calling
1670 # ota_from_target_files.py (since LMP).
1671 assert os.path.exists(path) and os.path.exists(mappath)
1672
1673 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1674 # it to clobbered_blocks so that it will be written to the target
1675 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1676 clobbered_blocks = "0"
1677
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001678 image = sparse_img.SparseImage(
1679 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1680 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001681
1682 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1683 # if they contain all zeros. We can't reconstruct such a file from its block
1684 # list. Tag such entries accordingly. (Bug: 65213616)
1685 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001686 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001687 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001688 continue
1689
Tom Cherryd14b8952018-08-09 14:26:00 -07001690 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1691 # filename listed in system.map may contain an additional leading slash
1692 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1693 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001694 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001695
Tom Cherryd14b8952018-08-09 14:26:00 -07001696 # Special handling another case, where files not under /system
1697 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001698 if which == 'system' and not arcname.startswith('SYSTEM'):
1699 arcname = 'ROOT/' + arcname
1700
1701 assert arcname in input_zip.namelist(), \
1702 "Failed to find the ZIP entry for {}".format(entry)
1703
Tao Baoc765cca2018-01-31 17:32:40 -08001704 info = input_zip.getinfo(arcname)
1705 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001706
1707 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001708 # image, check the original block list to determine its completeness. Note
1709 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001710 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001711 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001712
Tao Baoc765cca2018-01-31 17:32:40 -08001713 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1714 ranges.extra['incomplete'] = True
1715
1716 return image
1717
1718
Doug Zongkereef39442009-04-02 12:14:19 -07001719def GetKeyPasswords(keylist):
1720 """Given a list of keys, prompt the user to enter passwords for
1721 those which require them. Return a {key: password} dict. password
1722 will be None if the key has no password."""
1723
Doug Zongker8ce7c252009-05-22 13:34:54 -07001724 no_passwords = []
1725 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001726 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001727 devnull = open("/dev/null", "w+b")
1728 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001729 # We don't need a password for things that aren't really keys.
1730 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001731 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001732 continue
1733
T.R. Fullhart37e10522013-03-18 10:31:26 -07001734 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001735 "-inform", "DER", "-nocrypt"],
1736 stdin=devnull.fileno(),
1737 stdout=devnull.fileno(),
1738 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001739 p.communicate()
1740 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001741 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001742 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001743 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001744 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1745 "-inform", "DER", "-passin", "pass:"],
1746 stdin=devnull.fileno(),
1747 stdout=devnull.fileno(),
1748 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001749 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001750 if p.returncode == 0:
1751 # Encrypted key with empty string as password.
1752 key_passwords[k] = ''
1753 elif stderr.startswith('Error decrypting key'):
1754 # Definitely encrypted key.
1755 # It would have said "Error reading key" if it didn't parse correctly.
1756 need_passwords.append(k)
1757 else:
1758 # Potentially, a type of key that openssl doesn't understand.
1759 # We'll let the routines in signapk.jar handle it.
1760 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001761 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001762
T.R. Fullhart37e10522013-03-18 10:31:26 -07001763 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001764 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001765 return key_passwords
1766
1767
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001768def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001769 """Gets the minSdkVersion declared in the APK.
1770
changho.shin0f125362019-07-08 10:59:00 +09001771 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001772 This can be both a decimal number (API Level) or a codename.
1773
1774 Args:
1775 apk_name: The APK filename.
1776
1777 Returns:
1778 The parsed SDK version string.
1779
1780 Raises:
1781 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001782 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001783 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001784 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001785 stderr=subprocess.PIPE)
1786 stdoutdata, stderrdata = proc.communicate()
1787 if proc.returncode != 0:
1788 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001789 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001790 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001791
Tao Baof47bf0f2018-03-21 23:28:51 -07001792 for line in stdoutdata.split("\n"):
1793 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001794 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1795 if m:
1796 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001797 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001798
1799
1800def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001801 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001802
Tao Baof47bf0f2018-03-21 23:28:51 -07001803 If minSdkVersion is set to a codename, it is translated to a number using the
1804 provided map.
1805
1806 Args:
1807 apk_name: The APK filename.
1808
1809 Returns:
1810 The parsed SDK version number.
1811
1812 Raises:
1813 ExternalError: On failing to get the min SDK version number.
1814 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001815 version = GetMinSdkVersion(apk_name)
1816 try:
1817 return int(version)
1818 except ValueError:
1819 # Not a decimal number. Codename?
1820 if version in codename_to_api_level_map:
1821 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04001822 raise ExternalError(
1823 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1824 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001825
1826
1827def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001828 codename_to_api_level_map=None, whole_file=False,
1829 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001830 """Sign the input_name zip/jar/apk, producing output_name. Use the
1831 given key and password (the latter may be None if the key does not
1832 have a password.
1833
Doug Zongker951495f2009-08-14 12:44:19 -07001834 If whole_file is true, use the "-w" option to SignApk to embed a
1835 signature that covers the whole file in the archive comment of the
1836 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001837
1838 min_api_level is the API Level (int) of the oldest platform this file may end
1839 up on. If not specified for an APK, the API Level is obtained by interpreting
1840 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1841
1842 codename_to_api_level_map is needed to translate the codename which may be
1843 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001844
1845 Caller may optionally specify extra args to be passed to SignApk, which
1846 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001847 """
Tao Bao76def242017-11-21 09:25:31 -08001848 if codename_to_api_level_map is None:
1849 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001850 if extra_signapk_args is None:
1851 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001852
Alex Klyubin9667b182015-12-10 13:38:50 -08001853 java_library_path = os.path.join(
1854 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1855
Tao Baoe95540e2016-11-08 12:08:53 -08001856 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1857 ["-Djava.library.path=" + java_library_path,
1858 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001859 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001860 if whole_file:
1861 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001862
1863 min_sdk_version = min_api_level
1864 if min_sdk_version is None:
1865 if not whole_file:
1866 min_sdk_version = GetMinSdkVersionInt(
1867 input_name, codename_to_api_level_map)
1868 if min_sdk_version is not None:
1869 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1870
T.R. Fullhart37e10522013-03-18 10:31:26 -07001871 cmd.extend([key + OPTIONS.public_key_suffix,
1872 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001873 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001874
Tao Bao73dd4f42018-10-04 16:25:33 -07001875 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001876 if password is not None:
1877 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001878 stdoutdata, _ = proc.communicate(password)
1879 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001880 raise ExternalError(
1881 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001882 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001883
Doug Zongkereef39442009-04-02 12:14:19 -07001884
Doug Zongker37974732010-09-16 17:44:38 -07001885def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001886 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001887
Tao Bao9dd909e2017-11-14 11:27:32 -08001888 For non-AVB images, raise exception if the data is too big. Print a warning
1889 if the data is nearing the maximum size.
1890
1891 For AVB images, the actual image size should be identical to the limit.
1892
1893 Args:
1894 data: A string that contains all the data for the partition.
1895 target: The partition name. The ".img" suffix is optional.
1896 info_dict: The dict to be looked up for relevant info.
1897 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001898 if target.endswith(".img"):
1899 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001900 mount_point = "/" + target
1901
Ying Wangf8824af2014-06-03 14:07:27 -07001902 fs_type = None
1903 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001904 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001905 if mount_point == "/userdata":
1906 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001907 p = info_dict["fstab"][mount_point]
1908 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001909 device = p.device
1910 if "/" in device:
1911 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001912 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001913 if not fs_type or not limit:
1914 return
Doug Zongkereef39442009-04-02 12:14:19 -07001915
Andrew Boie0f9aec82012-02-14 09:32:52 -08001916 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001917 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1918 # path.
1919 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1920 if size != limit:
1921 raise ExternalError(
1922 "Mismatching image size for %s: expected %d actual %d" % (
1923 target, limit, size))
1924 else:
1925 pct = float(size) * 100.0 / limit
1926 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1927 if pct >= 99.0:
1928 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04001929
1930 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001931 logger.warning("\n WARNING: %s\n", msg)
1932 else:
1933 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001934
1935
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001936def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001937 """Parses the APK certs info from a given target-files zip.
1938
1939 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1940 tuple with the following elements: (1) a dictionary that maps packages to
1941 certs (based on the "certificate" and "private_key" attributes in the file;
1942 (2) a string representing the extension of compressed APKs in the target files
1943 (e.g ".gz", ".bro").
1944
1945 Args:
1946 tf_zip: The input target_files ZipFile (already open).
1947
1948 Returns:
1949 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1950 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1951 no compressed APKs.
1952 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001953 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001954 compressed_extension = None
1955
Tao Bao0f990332017-09-08 19:02:54 -07001956 # META/apkcerts.txt contains the info for _all_ the packages known at build
1957 # time. Filter out the ones that are not installed.
1958 installed_files = set()
1959 for name in tf_zip.namelist():
1960 basename = os.path.basename(name)
1961 if basename:
1962 installed_files.add(basename)
1963
Tao Baoda30cfa2017-12-01 16:19:46 -08001964 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001965 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001966 if not line:
1967 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001968 m = re.match(
1969 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001970 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1971 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001972 line)
1973 if not m:
1974 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001975
Tao Bao818ddf52018-01-05 11:17:34 -08001976 matches = m.groupdict()
1977 cert = matches["CERT"]
1978 privkey = matches["PRIVKEY"]
1979 name = matches["NAME"]
1980 this_compressed_extension = matches["COMPRESSED"]
1981
1982 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1983 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1984 if cert in SPECIAL_CERT_STRINGS and not privkey:
1985 certmap[name] = cert
1986 elif (cert.endswith(OPTIONS.public_key_suffix) and
1987 privkey.endswith(OPTIONS.private_key_suffix) and
1988 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1989 certmap[name] = cert[:-public_key_suffix_len]
1990 else:
1991 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1992
1993 if not this_compressed_extension:
1994 continue
1995
1996 # Only count the installed files.
1997 filename = name + '.' + this_compressed_extension
1998 if filename not in installed_files:
1999 continue
2000
2001 # Make sure that all the values in the compression map have the same
2002 # extension. We don't support multiple compression methods in the same
2003 # system image.
2004 if compressed_extension:
2005 if this_compressed_extension != compressed_extension:
2006 raise ValueError(
2007 "Multiple compressed extensions: {} vs {}".format(
2008 compressed_extension, this_compressed_extension))
2009 else:
2010 compressed_extension = this_compressed_extension
2011
2012 return (certmap,
2013 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002014
2015
Doug Zongkereef39442009-04-02 12:14:19 -07002016COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002017Global options
2018
2019 -p (--path) <dir>
2020 Prepend <dir>/bin to the list of places to search for binaries run by this
2021 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002022
Doug Zongker05d3dea2009-06-22 11:32:31 -07002023 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002024 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002025
Tao Bao30df8b42018-04-23 15:32:53 -07002026 -x (--extra) <key=value>
2027 Add a key/value pair to the 'extras' dict, which device-specific extension
2028 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002029
Doug Zongkereef39442009-04-02 12:14:19 -07002030 -v (--verbose)
2031 Show command lines being executed.
2032
2033 -h (--help)
2034 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002035
2036 --logfile <file>
2037 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002038"""
2039
Kelvin Zhang0876c412020-06-23 15:06:58 -04002040
Doug Zongkereef39442009-04-02 12:14:19 -07002041def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002042 print(docstring.rstrip("\n"))
2043 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002044
2045
2046def ParseOptions(argv,
2047 docstring,
2048 extra_opts="", extra_long_opts=(),
2049 extra_option_handler=None):
2050 """Parse the options in argv and return any arguments that aren't
2051 flags. docstring is the calling module's docstring, to be displayed
2052 for errors and -h. extra_opts and extra_long_opts are for flags
2053 defined by the caller, which are processed by passing them to
2054 extra_option_handler."""
2055
2056 try:
2057 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002058 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002059 ["help", "verbose", "path=", "signapk_path=",
2060 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002061 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002062 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2063 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002064 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2065 "aftl_key_path=", "aftl_manufacturer_key_path=",
2066 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002067 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002068 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002069 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002070 sys.exit(2)
2071
Doug Zongkereef39442009-04-02 12:14:19 -07002072 for o, a in opts:
2073 if o in ("-h", "--help"):
2074 Usage(docstring)
2075 sys.exit()
2076 elif o in ("-v", "--verbose"):
2077 OPTIONS.verbose = True
2078 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002079 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002080 elif o in ("--signapk_path",):
2081 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002082 elif o in ("--signapk_shared_library_path",):
2083 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002084 elif o in ("--extra_signapk_args",):
2085 OPTIONS.extra_signapk_args = shlex.split(a)
2086 elif o in ("--java_path",):
2087 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002088 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002089 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002090 elif o in ("--android_jar_path",):
2091 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002092 elif o in ("--public_key_suffix",):
2093 OPTIONS.public_key_suffix = a
2094 elif o in ("--private_key_suffix",):
2095 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002096 elif o in ("--boot_signer_path",):
2097 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002098 elif o in ("--boot_signer_args",):
2099 OPTIONS.boot_signer_args = shlex.split(a)
2100 elif o in ("--verity_signer_path",):
2101 OPTIONS.verity_signer_path = a
2102 elif o in ("--verity_signer_args",):
2103 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002104 elif o in ("--aftl_tool_path",):
2105 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002106 elif o in ("--aftl_server",):
2107 OPTIONS.aftl_server = a
2108 elif o in ("--aftl_key_path",):
2109 OPTIONS.aftl_key_path = a
2110 elif o in ("--aftl_manufacturer_key_path",):
2111 OPTIONS.aftl_manufacturer_key_path = a
2112 elif o in ("--aftl_signer_helper",):
2113 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002114 elif o in ("-s", "--device_specific"):
2115 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002116 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002117 key, value = a.split("=", 1)
2118 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002119 elif o in ("--logfile",):
2120 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002121 else:
2122 if extra_option_handler is None or not extra_option_handler(o, a):
2123 assert False, "unknown option \"%s\"" % (o,)
2124
Doug Zongker85448772014-09-09 14:59:20 -07002125 if OPTIONS.search_path:
2126 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2127 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002128
2129 return args
2130
2131
Tao Bao4c851b12016-09-19 13:54:38 -07002132def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002133 """Make a temp file and add it to the list of things to be deleted
2134 when Cleanup() is called. Return the filename."""
2135 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2136 os.close(fd)
2137 OPTIONS.tempfiles.append(fn)
2138 return fn
2139
2140
Tao Bao1c830bf2017-12-25 10:43:47 -08002141def MakeTempDir(prefix='tmp', suffix=''):
2142 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2143
2144 Returns:
2145 The absolute pathname of the new directory.
2146 """
2147 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2148 OPTIONS.tempfiles.append(dir_name)
2149 return dir_name
2150
2151
Doug Zongkereef39442009-04-02 12:14:19 -07002152def Cleanup():
2153 for i in OPTIONS.tempfiles:
2154 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002155 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002156 else:
2157 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002158 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002159
2160
2161class PasswordManager(object):
2162 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002163 self.editor = os.getenv("EDITOR")
2164 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002165
2166 def GetPasswords(self, items):
2167 """Get passwords corresponding to each string in 'items',
2168 returning a dict. (The dict may have keys in addition to the
2169 values in 'items'.)
2170
2171 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2172 user edit that file to add more needed passwords. If no editor is
2173 available, or $ANDROID_PW_FILE isn't define, prompts the user
2174 interactively in the ordinary way.
2175 """
2176
2177 current = self.ReadFile()
2178
2179 first = True
2180 while True:
2181 missing = []
2182 for i in items:
2183 if i not in current or not current[i]:
2184 missing.append(i)
2185 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002186 if not missing:
2187 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002188
2189 for i in missing:
2190 current[i] = ""
2191
2192 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002193 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002194 if sys.version_info[0] >= 3:
2195 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002196 answer = raw_input("try to edit again? [y]> ").strip()
2197 if answer and answer[0] not in 'yY':
2198 raise RuntimeError("key passwords unavailable")
2199 first = False
2200
2201 current = self.UpdateAndReadFile(current)
2202
Kelvin Zhang0876c412020-06-23 15:06:58 -04002203 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002204 """Prompt the user to enter a value (password) for each key in
2205 'current' whose value is fales. Returns a new dict with all the
2206 values.
2207 """
2208 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002209 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002210 if v:
2211 result[k] = v
2212 else:
2213 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002214 result[k] = getpass.getpass(
2215 "Enter password for %s key> " % k).strip()
2216 if result[k]:
2217 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002218 return result
2219
2220 def UpdateAndReadFile(self, current):
2221 if not self.editor or not self.pwfile:
2222 return self.PromptResult(current)
2223
2224 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002225 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002226 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2227 f.write("# (Additional spaces are harmless.)\n\n")
2228
2229 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002230 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002231 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002232 f.write("[[[ %s ]]] %s\n" % (v, k))
2233 if not v and first_line is None:
2234 # position cursor on first line with no password.
2235 first_line = i + 4
2236 f.close()
2237
Tao Bao986ee862018-10-04 15:46:16 -07002238 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002239
2240 return self.ReadFile()
2241
2242 def ReadFile(self):
2243 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002244 if self.pwfile is None:
2245 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002246 try:
2247 f = open(self.pwfile, "r")
2248 for line in f:
2249 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002250 if not line or line[0] == '#':
2251 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002252 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2253 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002254 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002255 else:
2256 result[m.group(2)] = m.group(1)
2257 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002258 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002259 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002260 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002261 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002262
2263
Dan Albert8e0178d2015-01-27 15:53:15 -08002264def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2265 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002266
2267 # http://b/18015246
2268 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2269 # for files larger than 2GiB. We can work around this by adjusting their
2270 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2271 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2272 # it isn't clear to me exactly what circumstances cause this).
2273 # `zipfile.write()` must be used directly to work around this.
2274 #
2275 # This mess can be avoided if we port to python3.
2276 saved_zip64_limit = zipfile.ZIP64_LIMIT
2277 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2278
2279 if compress_type is None:
2280 compress_type = zip_file.compression
2281 if arcname is None:
2282 arcname = filename
2283
2284 saved_stat = os.stat(filename)
2285
2286 try:
2287 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2288 # file to be zipped and reset it when we're done.
2289 os.chmod(filename, perms)
2290
2291 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002292 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2293 # intentional. zip stores datetimes in local time without a time zone
2294 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2295 # in the zip archive.
2296 local_epoch = datetime.datetime.fromtimestamp(0)
2297 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002298 os.utime(filename, (timestamp, timestamp))
2299
2300 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2301 finally:
2302 os.chmod(filename, saved_stat.st_mode)
2303 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2304 zipfile.ZIP64_LIMIT = saved_zip64_limit
2305
2306
Tao Bao58c1b962015-05-20 09:32:18 -07002307def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002308 compress_type=None):
2309 """Wrap zipfile.writestr() function to work around the zip64 limit.
2310
2311 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2312 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2313 when calling crc32(bytes).
2314
2315 But it still works fine to write a shorter string into a large zip file.
2316 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2317 when we know the string won't be too long.
2318 """
2319
2320 saved_zip64_limit = zipfile.ZIP64_LIMIT
2321 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2322
2323 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2324 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002325 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002326 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002327 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002328 else:
Tao Baof3282b42015-04-01 11:21:55 -07002329 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002330 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2331 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2332 # such a case (since
2333 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2334 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2335 # permission bits. We follow the logic in Python 3 to get consistent
2336 # behavior between using the two versions.
2337 if not zinfo.external_attr:
2338 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002339
2340 # If compress_type is given, it overrides the value in zinfo.
2341 if compress_type is not None:
2342 zinfo.compress_type = compress_type
2343
Tao Bao58c1b962015-05-20 09:32:18 -07002344 # If perms is given, it has a priority.
2345 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002346 # If perms doesn't set the file type, mark it as a regular file.
2347 if perms & 0o770000 == 0:
2348 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002349 zinfo.external_attr = perms << 16
2350
Tao Baof3282b42015-04-01 11:21:55 -07002351 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002352 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2353
Dan Albert8b72aef2015-03-23 19:13:21 -07002354 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002355 zipfile.ZIP64_LIMIT = saved_zip64_limit
2356
2357
Tao Bao89d7ab22017-12-14 17:05:33 -08002358def ZipDelete(zip_filename, entries):
2359 """Deletes entries from a ZIP file.
2360
2361 Since deleting entries from a ZIP file is not supported, it shells out to
2362 'zip -d'.
2363
2364 Args:
2365 zip_filename: The name of the ZIP file.
2366 entries: The name of the entry, or the list of names to be deleted.
2367
2368 Raises:
2369 AssertionError: In case of non-zero return from 'zip'.
2370 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002371 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002372 entries = [entries]
2373 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002374 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002375
2376
Tao Baof3282b42015-04-01 11:21:55 -07002377def ZipClose(zip_file):
2378 # http://b/18015246
2379 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2380 # central directory.
2381 saved_zip64_limit = zipfile.ZIP64_LIMIT
2382 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2383
2384 zip_file.close()
2385
2386 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002387
2388
2389class DeviceSpecificParams(object):
2390 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002391
Doug Zongker05d3dea2009-06-22 11:32:31 -07002392 def __init__(self, **kwargs):
2393 """Keyword arguments to the constructor become attributes of this
2394 object, which is passed to all functions in the device-specific
2395 module."""
Tao Bao38884282019-07-10 22:20:56 -07002396 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002397 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002398 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002399
2400 if self.module is None:
2401 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002402 if not path:
2403 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002404 try:
2405 if os.path.isdir(path):
2406 info = imp.find_module("releasetools", [path])
2407 else:
2408 d, f = os.path.split(path)
2409 b, x = os.path.splitext(f)
2410 if x == ".py":
2411 f = b
2412 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002413 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002414 self.module = imp.load_module("device_specific", *info)
2415 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002416 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002417
2418 def _DoCall(self, function_name, *args, **kwargs):
2419 """Call the named function in the device-specific module, passing
2420 the given args and kwargs. The first argument to the call will be
2421 the DeviceSpecific object itself. If there is no module, or the
2422 module does not define the function, return the value of the
2423 'default' kwarg (which itself defaults to None)."""
2424 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002425 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002426 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2427
2428 def FullOTA_Assertions(self):
2429 """Called after emitting the block of assertions at the top of a
2430 full OTA package. Implementations can add whatever additional
2431 assertions they like."""
2432 return self._DoCall("FullOTA_Assertions")
2433
Doug Zongkere5ff5902012-01-17 10:55:37 -08002434 def FullOTA_InstallBegin(self):
2435 """Called at the start of full OTA installation."""
2436 return self._DoCall("FullOTA_InstallBegin")
2437
Yifan Hong10c530d2018-12-27 17:34:18 -08002438 def FullOTA_GetBlockDifferences(self):
2439 """Called during full OTA installation and verification.
2440 Implementation should return a list of BlockDifference objects describing
2441 the update on each additional partitions.
2442 """
2443 return self._DoCall("FullOTA_GetBlockDifferences")
2444
Doug Zongker05d3dea2009-06-22 11:32:31 -07002445 def FullOTA_InstallEnd(self):
2446 """Called at the end of full OTA installation; typically this is
2447 used to install the image for the device's baseband processor."""
2448 return self._DoCall("FullOTA_InstallEnd")
2449
2450 def IncrementalOTA_Assertions(self):
2451 """Called after emitting the block of assertions at the top of an
2452 incremental OTA package. Implementations can add whatever
2453 additional assertions they like."""
2454 return self._DoCall("IncrementalOTA_Assertions")
2455
Doug Zongkere5ff5902012-01-17 10:55:37 -08002456 def IncrementalOTA_VerifyBegin(self):
2457 """Called at the start of the verification phase of incremental
2458 OTA installation; additional checks can be placed here to abort
2459 the script before any changes are made."""
2460 return self._DoCall("IncrementalOTA_VerifyBegin")
2461
Doug Zongker05d3dea2009-06-22 11:32:31 -07002462 def IncrementalOTA_VerifyEnd(self):
2463 """Called at the end of the verification phase of incremental OTA
2464 installation; additional checks can be placed here to abort the
2465 script before any changes are made."""
2466 return self._DoCall("IncrementalOTA_VerifyEnd")
2467
Doug Zongkere5ff5902012-01-17 10:55:37 -08002468 def IncrementalOTA_InstallBegin(self):
2469 """Called at the start of incremental OTA installation (after
2470 verification is complete)."""
2471 return self._DoCall("IncrementalOTA_InstallBegin")
2472
Yifan Hong10c530d2018-12-27 17:34:18 -08002473 def IncrementalOTA_GetBlockDifferences(self):
2474 """Called during incremental OTA installation and verification.
2475 Implementation should return a list of BlockDifference objects describing
2476 the update on each additional partitions.
2477 """
2478 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2479
Doug Zongker05d3dea2009-06-22 11:32:31 -07002480 def IncrementalOTA_InstallEnd(self):
2481 """Called at the end of incremental OTA installation; typically
2482 this is used to install the image for the device's baseband
2483 processor."""
2484 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002485
Tao Bao9bc6bb22015-11-09 16:58:28 -08002486 def VerifyOTA_Assertions(self):
2487 return self._DoCall("VerifyOTA_Assertions")
2488
Tao Bao76def242017-11-21 09:25:31 -08002489
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002490class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002491 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002492 self.name = name
2493 self.data = data
2494 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002495 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002496 self.sha1 = sha1(data).hexdigest()
2497
2498 @classmethod
2499 def FromLocalFile(cls, name, diskname):
2500 f = open(diskname, "rb")
2501 data = f.read()
2502 f.close()
2503 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002504
2505 def WriteToTemp(self):
2506 t = tempfile.NamedTemporaryFile()
2507 t.write(self.data)
2508 t.flush()
2509 return t
2510
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002511 def WriteToDir(self, d):
2512 with open(os.path.join(d, self.name), "wb") as fp:
2513 fp.write(self.data)
2514
Geremy Condra36bd3652014-02-06 19:45:10 -08002515 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002516 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002517
Tao Bao76def242017-11-21 09:25:31 -08002518
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002519DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002520 ".gz": "imgdiff",
2521 ".zip": ["imgdiff", "-z"],
2522 ".jar": ["imgdiff", "-z"],
2523 ".apk": ["imgdiff", "-z"],
2524 ".img": "imgdiff",
2525}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002526
Tao Bao76def242017-11-21 09:25:31 -08002527
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002528class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002529 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002530 self.tf = tf
2531 self.sf = sf
2532 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002533 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002534
2535 def ComputePatch(self):
2536 """Compute the patch (as a string of data) needed to turn sf into
2537 tf. Returns the same tuple as GetPatch()."""
2538
2539 tf = self.tf
2540 sf = self.sf
2541
Doug Zongker24cd2802012-08-14 16:36:15 -07002542 if self.diff_program:
2543 diff_program = self.diff_program
2544 else:
2545 ext = os.path.splitext(tf.name)[1]
2546 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002547
2548 ttemp = tf.WriteToTemp()
2549 stemp = sf.WriteToTemp()
2550
2551 ext = os.path.splitext(tf.name)[1]
2552
2553 try:
2554 ptemp = tempfile.NamedTemporaryFile()
2555 if isinstance(diff_program, list):
2556 cmd = copy.copy(diff_program)
2557 else:
2558 cmd = [diff_program]
2559 cmd.append(stemp.name)
2560 cmd.append(ttemp.name)
2561 cmd.append(ptemp.name)
2562 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002563 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002564
Doug Zongkerf8340082014-08-05 10:39:37 -07002565 def run():
2566 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002567 if e:
2568 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002569 th = threading.Thread(target=run)
2570 th.start()
2571 th.join(timeout=300) # 5 mins
2572 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002573 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002574 p.terminate()
2575 th.join(5)
2576 if th.is_alive():
2577 p.kill()
2578 th.join()
2579
Tianjie Xua2a9f992018-01-05 15:15:54 -08002580 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002581 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002582 self.patch = None
2583 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002584 diff = ptemp.read()
2585 finally:
2586 ptemp.close()
2587 stemp.close()
2588 ttemp.close()
2589
2590 self.patch = diff
2591 return self.tf, self.sf, self.patch
2592
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002593 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002594 """Returns a tuple of (target_file, source_file, patch_data).
2595
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002596 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002597 computing the patch failed.
2598 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002599 return self.tf, self.sf, self.patch
2600
2601
2602def ComputeDifferences(diffs):
2603 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002604 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002605
2606 # Do the largest files first, to try and reduce the long-pole effect.
2607 by_size = [(i.tf.size, i) for i in diffs]
2608 by_size.sort(reverse=True)
2609 by_size = [i[1] for i in by_size]
2610
2611 lock = threading.Lock()
2612 diff_iter = iter(by_size) # accessed under lock
2613
2614 def worker():
2615 try:
2616 lock.acquire()
2617 for d in diff_iter:
2618 lock.release()
2619 start = time.time()
2620 d.ComputePatch()
2621 dur = time.time() - start
2622 lock.acquire()
2623
2624 tf, sf, patch = d.GetPatch()
2625 if sf.name == tf.name:
2626 name = tf.name
2627 else:
2628 name = "%s (%s)" % (tf.name, sf.name)
2629 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002630 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002631 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002632 logger.info(
2633 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2634 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002635 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002636 except Exception:
2637 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002638 raise
2639
2640 # start worker threads; wait for them all to finish.
2641 threads = [threading.Thread(target=worker)
2642 for i in range(OPTIONS.worker_threads)]
2643 for th in threads:
2644 th.start()
2645 while threads:
2646 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002647
2648
Dan Albert8b72aef2015-03-23 19:13:21 -07002649class BlockDifference(object):
2650 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002651 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002652 self.tgt = tgt
2653 self.src = src
2654 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002655 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002656 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002657
Tao Baodd2a5892015-03-12 12:32:37 -07002658 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002659 version = max(
2660 int(i) for i in
2661 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002662 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002663 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002664
Tianjie Xu41976c72019-07-03 13:57:01 -07002665 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2666 version=self.version,
2667 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002668 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002669 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002670 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002671 self.touched_src_ranges = b.touched_src_ranges
2672 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002673
Yifan Hong10c530d2018-12-27 17:34:18 -08002674 # On devices with dynamic partitions, for new partitions,
2675 # src is None but OPTIONS.source_info_dict is not.
2676 if OPTIONS.source_info_dict is None:
2677 is_dynamic_build = OPTIONS.info_dict.get(
2678 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002679 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002680 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002681 is_dynamic_build = OPTIONS.source_info_dict.get(
2682 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002683 is_dynamic_source = partition in shlex.split(
2684 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002685
Yifan Hongbb2658d2019-01-25 12:30:58 -08002686 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002687 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2688
Yifan Hongbb2658d2019-01-25 12:30:58 -08002689 # For dynamic partitions builds, check partition list in both source
2690 # and target build because new partitions may be added, and existing
2691 # partitions may be removed.
2692 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2693
Yifan Hong10c530d2018-12-27 17:34:18 -08002694 if is_dynamic:
2695 self.device = 'map_partition("%s")' % partition
2696 else:
2697 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002698 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2699 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002700 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002701 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2702 OPTIONS.source_info_dict)
2703 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002704
Tao Baod8d14be2016-02-04 14:26:02 -08002705 @property
2706 def required_cache(self):
2707 return self._required_cache
2708
Tao Bao76def242017-11-21 09:25:31 -08002709 def WriteScript(self, script, output_zip, progress=None,
2710 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002711 if not self.src:
2712 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002713 script.Print("Patching %s image unconditionally..." % (self.partition,))
2714 else:
2715 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002716
Dan Albert8b72aef2015-03-23 19:13:21 -07002717 if progress:
2718 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002719 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002720
2721 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002722 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002723
Tao Bao9bc6bb22015-11-09 16:58:28 -08002724 def WriteStrictVerifyScript(self, script):
2725 """Verify all the blocks in the care_map, including clobbered blocks.
2726
2727 This differs from the WriteVerifyScript() function: a) it prints different
2728 error messages; b) it doesn't allow half-way updated images to pass the
2729 verification."""
2730
2731 partition = self.partition
2732 script.Print("Verifying %s..." % (partition,))
2733 ranges = self.tgt.care_map
2734 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002735 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002736 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2737 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002738 self.device, ranges_str,
2739 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002740 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002741 script.AppendExtra("")
2742
Tao Baod522bdc2016-04-12 15:53:16 -07002743 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002744 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002745
2746 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002747 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002748 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002749
2750 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002751 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002752 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002753 ranges = self.touched_src_ranges
2754 expected_sha1 = self.touched_src_sha1
2755 else:
2756 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2757 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002758
2759 # No blocks to be checked, skipping.
2760 if not ranges:
2761 return
2762
Tao Bao5ece99d2015-05-12 11:42:31 -07002763 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002764 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002765 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002766 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2767 '"%s.patch.dat")) then' % (
2768 self.device, ranges_str, expected_sha1,
2769 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002770 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002771 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002772
Tianjie Xufc3422a2015-12-15 11:53:59 -08002773 if self.version >= 4:
2774
2775 # Bug: 21124327
2776 # When generating incrementals for the system and vendor partitions in
2777 # version 4 or newer, explicitly check the first block (which contains
2778 # the superblock) of the partition to see if it's what we expect. If
2779 # this check fails, give an explicit log message about the partition
2780 # having been remounted R/W (the most likely explanation).
2781 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002782 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002783
2784 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002785 if partition == "system":
2786 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2787 else:
2788 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002789 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002790 'ifelse (block_image_recover({device}, "{ranges}") && '
2791 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002792 'package_extract_file("{partition}.transfer.list"), '
2793 '"{partition}.new.dat", "{partition}.patch.dat"), '
2794 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002795 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002796 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002797 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002798
Tao Baodd2a5892015-03-12 12:32:37 -07002799 # Abort the OTA update. Note that the incremental OTA cannot be applied
2800 # even if it may match the checksum of the target partition.
2801 # a) If version < 3, operations like move and erase will make changes
2802 # unconditionally and damage the partition.
2803 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002804 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002805 if partition == "system":
2806 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2807 else:
2808 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2809 script.AppendExtra((
2810 'abort("E%d: %s partition has unexpected contents");\n'
2811 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002812
Yifan Hong10c530d2018-12-27 17:34:18 -08002813 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002814 partition = self.partition
2815 script.Print('Verifying the updated %s image...' % (partition,))
2816 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2817 ranges = self.tgt.care_map
2818 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002819 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002820 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002821 self.device, ranges_str,
2822 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002823
2824 # Bug: 20881595
2825 # Verify that extended blocks are really zeroed out.
2826 if self.tgt.extended:
2827 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002828 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002829 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002830 self.device, ranges_str,
2831 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002832 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002833 if partition == "system":
2834 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2835 else:
2836 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002837 script.AppendExtra(
2838 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002839 ' abort("E%d: %s partition has unexpected non-zero contents after '
2840 'OTA update");\n'
2841 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002842 else:
2843 script.Print('Verified the updated %s image.' % (partition,))
2844
Tianjie Xu209db462016-05-24 17:34:52 -07002845 if partition == "system":
2846 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2847 else:
2848 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2849
Tao Bao5fcaaef2015-06-01 13:40:49 -07002850 script.AppendExtra(
2851 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002852 ' abort("E%d: %s partition has unexpected contents after OTA '
2853 'update");\n'
2854 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002855
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002856 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002857 ZipWrite(output_zip,
2858 '{}.transfer.list'.format(self.path),
2859 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002860
Tao Bao76def242017-11-21 09:25:31 -08002861 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2862 # its size. Quailty 9 almost triples the compression time but doesn't
2863 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002864 # zip | brotli(quality 6) | brotli(quality 9)
2865 # compressed_size: 942M | 869M (~8% reduced) | 854M
2866 # compression_time: 75s | 265s | 719s
2867 # decompression_time: 15s | 25s | 25s
2868
2869 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002870 brotli_cmd = ['brotli', '--quality=6',
2871 '--output={}.new.dat.br'.format(self.path),
2872 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002873 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002874 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002875
2876 new_data_name = '{}.new.dat.br'.format(self.partition)
2877 ZipWrite(output_zip,
2878 '{}.new.dat.br'.format(self.path),
2879 new_data_name,
2880 compress_type=zipfile.ZIP_STORED)
2881 else:
2882 new_data_name = '{}.new.dat'.format(self.partition)
2883 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2884
Dan Albert8e0178d2015-01-27 15:53:15 -08002885 ZipWrite(output_zip,
2886 '{}.patch.dat'.format(self.path),
2887 '{}.patch.dat'.format(self.partition),
2888 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002889
Tianjie Xu209db462016-05-24 17:34:52 -07002890 if self.partition == "system":
2891 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2892 else:
2893 code = ErrorCode.VENDOR_UPDATE_FAILURE
2894
Yifan Hong10c530d2018-12-27 17:34:18 -08002895 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002896 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002897 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002898 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002899 device=self.device, partition=self.partition,
2900 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002901 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002902
Kelvin Zhang0876c412020-06-23 15:06:58 -04002903 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002904 data = source.ReadRangeSet(ranges)
2905 ctx = sha1()
2906
2907 for p in data:
2908 ctx.update(p)
2909
2910 return ctx.hexdigest()
2911
Kelvin Zhang0876c412020-06-23 15:06:58 -04002912 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07002913 """Return the hash value for all zero blocks."""
2914 zero_block = '\x00' * 4096
2915 ctx = sha1()
2916 for _ in range(num_blocks):
2917 ctx.update(zero_block)
2918
2919 return ctx.hexdigest()
2920
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002921
Tianjie Xu41976c72019-07-03 13:57:01 -07002922# Expose these two classes to support vendor-specific scripts
2923DataImage = images.DataImage
2924EmptyImage = images.EmptyImage
2925
Tao Bao76def242017-11-21 09:25:31 -08002926
Doug Zongker96a57e72010-09-26 14:57:41 -07002927# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002928PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002929 "ext4": "EMMC",
2930 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002931 "f2fs": "EMMC",
2932 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002933}
Doug Zongker96a57e72010-09-26 14:57:41 -07002934
Kelvin Zhang0876c412020-06-23 15:06:58 -04002935
Yifan Hongbdb32012020-05-07 12:38:53 -07002936def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2937 """
2938 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2939 backwards compatibility. It aborts if the fstab entry has slotselect option
2940 (unless check_no_slot is explicitly set to False).
2941 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002942 fstab = info["fstab"]
2943 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07002944 if check_no_slot:
2945 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002946 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002947 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2948 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002949 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002950
2951
Yifan Hongbdb32012020-05-07 12:38:53 -07002952def GetTypeAndDeviceExpr(mount_point, info):
2953 """
2954 Return the filesystem of the partition, and an edify expression that evaluates
2955 to the device at runtime.
2956 """
2957 fstab = info["fstab"]
2958 if fstab:
2959 p = fstab[mount_point]
2960 device_expr = '"%s"' % fstab[mount_point].device
2961 if p.slotselect:
2962 device_expr = 'add_slot_suffix(%s)' % device_expr
2963 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002964 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07002965
2966
2967def GetEntryForDevice(fstab, device):
2968 """
2969 Returns:
2970 The first entry in fstab whose device is the given value.
2971 """
2972 if not fstab:
2973 return None
2974 for mount_point in fstab:
2975 if fstab[mount_point].device == device:
2976 return fstab[mount_point]
2977 return None
2978
Kelvin Zhang0876c412020-06-23 15:06:58 -04002979
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002980def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002981 """Parses and converts a PEM-encoded certificate into DER-encoded.
2982
2983 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2984
2985 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002986 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002987 """
2988 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002989 save = False
2990 for line in data.split("\n"):
2991 if "--END CERTIFICATE--" in line:
2992 break
2993 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002994 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002995 if "--BEGIN CERTIFICATE--" in line:
2996 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002997 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002998 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002999
Tao Bao04e1f012018-02-04 12:13:35 -08003000
3001def ExtractPublicKey(cert):
3002 """Extracts the public key (PEM-encoded) from the given certificate file.
3003
3004 Args:
3005 cert: The certificate filename.
3006
3007 Returns:
3008 The public key string.
3009
3010 Raises:
3011 AssertionError: On non-zero return from 'openssl'.
3012 """
3013 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3014 # While openssl 1.1 writes the key into the given filename followed by '-out',
3015 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3016 # stdout instead.
3017 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3018 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3019 pubkey, stderrdata = proc.communicate()
3020 assert proc.returncode == 0, \
3021 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3022 return pubkey
3023
3024
Tao Bao1ac886e2019-06-26 11:58:22 -07003025def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003026 """Extracts the AVB public key from the given public or private key.
3027
3028 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003029 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003030 key: The input key file, which should be PEM-encoded public or private key.
3031
3032 Returns:
3033 The path to the extracted AVB public key file.
3034 """
3035 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3036 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003037 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003038 return output
3039
3040
Doug Zongker412c02f2014-02-13 10:58:24 -08003041def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3042 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003043 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003044
Tao Bao6d5d6232018-03-09 17:04:42 -08003045 Most of the space in the boot and recovery images is just the kernel, which is
3046 identical for the two, so the resulting patch should be efficient. Add it to
3047 the output zip, along with a shell script that is run from init.rc on first
3048 boot to actually do the patching and install the new recovery image.
3049
3050 Args:
3051 input_dir: The top-level input directory of the target-files.zip.
3052 output_sink: The callback function that writes the result.
3053 recovery_img: File object for the recovery image.
3054 boot_img: File objects for the boot image.
3055 info_dict: A dict returned by common.LoadInfoDict() on the input
3056 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003057 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003058 if info_dict is None:
3059 info_dict = OPTIONS.info_dict
3060
Tao Bao6d5d6232018-03-09 17:04:42 -08003061 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003062 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3063
3064 if board_uses_vendorimage:
3065 # In this case, the output sink is rooted at VENDOR
3066 recovery_img_path = "etc/recovery.img"
3067 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3068 sh_dir = "bin"
3069 else:
3070 # In this case the output sink is rooted at SYSTEM
3071 recovery_img_path = "vendor/etc/recovery.img"
3072 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3073 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003074
Tao Baof2cffbd2015-07-22 12:33:18 -07003075 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003076 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003077
3078 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003079 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003080 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003081 # With system-root-image, boot and recovery images will have mismatching
3082 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3083 # to handle such a case.
3084 if system_root_image:
3085 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003086 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003087 assert not os.path.exists(path)
3088 else:
3089 diff_program = ["imgdiff"]
3090 if os.path.exists(path):
3091 diff_program.append("-b")
3092 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003093 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003094 else:
3095 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003096
3097 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3098 _, _, patch = d.ComputePatch()
3099 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003100
Dan Albertebb19aa2015-03-27 19:11:53 -07003101 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003102 # The following GetTypeAndDevice()s need to use the path in the target
3103 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003104 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3105 check_no_slot=False)
3106 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3107 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003108 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003109 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003110
Tao Baof2cffbd2015-07-22 12:33:18 -07003111 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003112
3113 # Note that we use /vendor to refer to the recovery resources. This will
3114 # work for a separate vendor partition mounted at /vendor or a
3115 # /system/vendor subdirectory on the system partition, for which init will
3116 # create a symlink from /vendor to /system/vendor.
3117
3118 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003119if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3120 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003121 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003122 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3123 log -t recovery "Installing new recovery image: succeeded" || \\
3124 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003125else
3126 log -t recovery "Recovery image already installed"
3127fi
3128""" % {'type': recovery_type,
3129 'device': recovery_device,
3130 'sha1': recovery_img.sha1,
3131 'size': recovery_img.size}
3132 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003133 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003134if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3135 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003136 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003137 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3138 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3139 log -t recovery "Installing new recovery image: succeeded" || \\
3140 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003141else
3142 log -t recovery "Recovery image already installed"
3143fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003144""" % {'boot_size': boot_img.size,
3145 'boot_sha1': boot_img.sha1,
3146 'recovery_size': recovery_img.size,
3147 'recovery_sha1': recovery_img.sha1,
3148 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003149 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
3150 'recovery_type': recovery_type + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003151 'recovery_device': recovery_device,
3152 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003153
Bill Peckhame868aec2019-09-17 17:06:47 -07003154 # The install script location moved from /system/etc to /system/bin in the L
3155 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3156 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003157
Tao Bao32fcdab2018-10-12 10:30:39 -07003158 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003159
Tao Baoda30cfa2017-12-01 16:19:46 -08003160 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003161
3162
3163class DynamicPartitionUpdate(object):
3164 def __init__(self, src_group=None, tgt_group=None, progress=None,
3165 block_difference=None):
3166 self.src_group = src_group
3167 self.tgt_group = tgt_group
3168 self.progress = progress
3169 self.block_difference = block_difference
3170
3171 @property
3172 def src_size(self):
3173 if not self.block_difference:
3174 return 0
3175 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3176
3177 @property
3178 def tgt_size(self):
3179 if not self.block_difference:
3180 return 0
3181 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3182
3183 @staticmethod
3184 def _GetSparseImageSize(img):
3185 if not img:
3186 return 0
3187 return img.blocksize * img.total_blocks
3188
3189
3190class DynamicGroupUpdate(object):
3191 def __init__(self, src_size=None, tgt_size=None):
3192 # None: group does not exist. 0: no size limits.
3193 self.src_size = src_size
3194 self.tgt_size = tgt_size
3195
3196
3197class DynamicPartitionsDifference(object):
3198 def __init__(self, info_dict, block_diffs, progress_dict=None,
3199 source_info_dict=None):
3200 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003201 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003202
3203 self._remove_all_before_apply = False
3204 if source_info_dict is None:
3205 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003206 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003207
Tao Baof1113e92019-06-18 12:10:14 -07003208 block_diff_dict = collections.OrderedDict(
3209 [(e.partition, e) for e in block_diffs])
3210
Yifan Hong10c530d2018-12-27 17:34:18 -08003211 assert len(block_diff_dict) == len(block_diffs), \
3212 "Duplicated BlockDifference object for {}".format(
3213 [partition for partition, count in
3214 collections.Counter(e.partition for e in block_diffs).items()
3215 if count > 1])
3216
Yifan Hong79997e52019-01-23 16:56:19 -08003217 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003218
3219 for p, block_diff in block_diff_dict.items():
3220 self._partition_updates[p] = DynamicPartitionUpdate()
3221 self._partition_updates[p].block_difference = block_diff
3222
3223 for p, progress in progress_dict.items():
3224 if p in self._partition_updates:
3225 self._partition_updates[p].progress = progress
3226
3227 tgt_groups = shlex.split(info_dict.get(
3228 "super_partition_groups", "").strip())
3229 src_groups = shlex.split(source_info_dict.get(
3230 "super_partition_groups", "").strip())
3231
3232 for g in tgt_groups:
3233 for p in shlex.split(info_dict.get(
3234 "super_%s_partition_list" % g, "").strip()):
3235 assert p in self._partition_updates, \
3236 "{} is in target super_{}_partition_list but no BlockDifference " \
3237 "object is provided.".format(p, g)
3238 self._partition_updates[p].tgt_group = g
3239
3240 for g in src_groups:
3241 for p in shlex.split(source_info_dict.get(
3242 "super_%s_partition_list" % g, "").strip()):
3243 assert p in self._partition_updates, \
3244 "{} is in source super_{}_partition_list but no BlockDifference " \
3245 "object is provided.".format(p, g)
3246 self._partition_updates[p].src_group = g
3247
Yifan Hong45433e42019-01-18 13:55:25 -08003248 target_dynamic_partitions = set(shlex.split(info_dict.get(
3249 "dynamic_partition_list", "").strip()))
3250 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3251 if u.tgt_size)
3252 assert block_diffs_with_target == target_dynamic_partitions, \
3253 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3254 list(target_dynamic_partitions), list(block_diffs_with_target))
3255
3256 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3257 "dynamic_partition_list", "").strip()))
3258 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3259 if u.src_size)
3260 assert block_diffs_with_source == source_dynamic_partitions, \
3261 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3262 list(source_dynamic_partitions), list(block_diffs_with_source))
3263
Yifan Hong10c530d2018-12-27 17:34:18 -08003264 if self._partition_updates:
3265 logger.info("Updating dynamic partitions %s",
3266 self._partition_updates.keys())
3267
Yifan Hong79997e52019-01-23 16:56:19 -08003268 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003269
3270 for g in tgt_groups:
3271 self._group_updates[g] = DynamicGroupUpdate()
3272 self._group_updates[g].tgt_size = int(info_dict.get(
3273 "super_%s_group_size" % g, "0").strip())
3274
3275 for g in src_groups:
3276 if g not in self._group_updates:
3277 self._group_updates[g] = DynamicGroupUpdate()
3278 self._group_updates[g].src_size = int(source_info_dict.get(
3279 "super_%s_group_size" % g, "0").strip())
3280
3281 self._Compute()
3282
3283 def WriteScript(self, script, output_zip, write_verify_script=False):
3284 script.Comment('--- Start patching dynamic partitions ---')
3285 for p, u in self._partition_updates.items():
3286 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3287 script.Comment('Patch partition %s' % p)
3288 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3289 write_verify_script=False)
3290
3291 op_list_path = MakeTempFile()
3292 with open(op_list_path, 'w') as f:
3293 for line in self._op_list:
3294 f.write('{}\n'.format(line))
3295
3296 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3297
3298 script.Comment('Update dynamic partition metadata')
3299 script.AppendExtra('assert(update_dynamic_partitions('
3300 'package_extract_file("dynamic_partitions_op_list")));')
3301
3302 if write_verify_script:
3303 for p, u in self._partition_updates.items():
3304 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3305 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003306 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003307
3308 for p, u in self._partition_updates.items():
3309 if u.tgt_size and u.src_size <= u.tgt_size:
3310 script.Comment('Patch partition %s' % p)
3311 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3312 write_verify_script=write_verify_script)
3313 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003314 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003315
3316 script.Comment('--- End patching dynamic partitions ---')
3317
3318 def _Compute(self):
3319 self._op_list = list()
3320
3321 def append(line):
3322 self._op_list.append(line)
3323
3324 def comment(line):
3325 self._op_list.append("# %s" % line)
3326
3327 if self._remove_all_before_apply:
3328 comment('Remove all existing dynamic partitions and groups before '
3329 'applying full OTA')
3330 append('remove_all_groups')
3331
3332 for p, u in self._partition_updates.items():
3333 if u.src_group and not u.tgt_group:
3334 append('remove %s' % p)
3335
3336 for p, u in self._partition_updates.items():
3337 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3338 comment('Move partition %s from %s to default' % (p, u.src_group))
3339 append('move %s default' % p)
3340
3341 for p, u in self._partition_updates.items():
3342 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3343 comment('Shrink partition %s from %d to %d' %
3344 (p, u.src_size, u.tgt_size))
3345 append('resize %s %s' % (p, u.tgt_size))
3346
3347 for g, u in self._group_updates.items():
3348 if u.src_size is not None and u.tgt_size is None:
3349 append('remove_group %s' % g)
3350 if (u.src_size is not None and u.tgt_size is not None and
3351 u.src_size > u.tgt_size):
3352 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3353 append('resize_group %s %d' % (g, u.tgt_size))
3354
3355 for g, u in self._group_updates.items():
3356 if u.src_size is None and u.tgt_size is not None:
3357 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3358 append('add_group %s %d' % (g, u.tgt_size))
3359 if (u.src_size is not None and u.tgt_size is not None and
3360 u.src_size < u.tgt_size):
3361 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3362 append('resize_group %s %d' % (g, u.tgt_size))
3363
3364 for p, u in self._partition_updates.items():
3365 if u.tgt_group and not u.src_group:
3366 comment('Add partition %s to group %s' % (p, u.tgt_group))
3367 append('add %s %s' % (p, u.tgt_group))
3368
3369 for p, u in self._partition_updates.items():
3370 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003371 comment('Grow partition %s from %d to %d' %
3372 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003373 append('resize %s %d' % (p, u.tgt_size))
3374
3375 for p, u in self._partition_updates.items():
3376 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3377 comment('Move partition %s from default to %s' %
3378 (p, u.tgt_group))
3379 append('move %s %s' % (p, u.tgt_group))