blob: 89900d3eeafeb94551162b03848227812a808718 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
112# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700114 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm',
115 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800116
Tao Bao08c190f2019-06-03 23:07:58 -0700117# Chained VBMeta partitions.
118AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
119
Tianjie Xu861f4132018-09-12 11:49:33 -0700120# Partitions that should have their care_map added to META/care_map.pb
Yifan Hongcfb917a2020-05-07 14:58:20 -0700121PARTITIONS_WITH_CARE_MAP = (
122 'system',
123 'vendor',
124 'product',
125 'system_ext',
126 'odm',
127 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700128 'odm_dlkm',
Yifan Hongcfb917a2020-05-07 14:58:20 -0700129)
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
131
Tianjie Xu209db462016-05-24 17:34:52 -0700132class ErrorCode(object):
133 """Define error_codes for failures that happen during the actual
134 update package installation.
135
136 Error codes 0-999 are reserved for failures before the package
137 installation (i.e. low battery, package verification failure).
138 Detailed code in 'bootable/recovery/error_code.h' """
139
140 SYSTEM_VERIFICATION_FAILURE = 1000
141 SYSTEM_UPDATE_FAILURE = 1001
142 SYSTEM_UNEXPECTED_CONTENTS = 1002
143 SYSTEM_NONZERO_CONTENTS = 1003
144 SYSTEM_RECOVER_FAILURE = 1004
145 VENDOR_VERIFICATION_FAILURE = 2000
146 VENDOR_UPDATE_FAILURE = 2001
147 VENDOR_UNEXPECTED_CONTENTS = 2002
148 VENDOR_NONZERO_CONTENTS = 2003
149 VENDOR_RECOVER_FAILURE = 2004
150 OEM_PROP_MISMATCH = 3000
151 FINGERPRINT_MISMATCH = 3001
152 THUMBPRINT_MISMATCH = 3002
153 OLDER_BUILD = 3003
154 DEVICE_MISMATCH = 3004
155 BAD_PATCH_FILE = 3005
156 INSUFFICIENT_CACHE_SPACE = 3006
157 TUNE_PARTITION_FAILURE = 3007
158 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800159
Tao Bao80921982018-03-21 21:02:19 -0700160
Dan Albert8b72aef2015-03-23 19:13:21 -0700161class ExternalError(RuntimeError):
162 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700163
164
Tao Bao32fcdab2018-10-12 10:30:39 -0700165def InitLogging():
166 DEFAULT_LOGGING_CONFIG = {
167 'version': 1,
168 'disable_existing_loggers': False,
169 'formatters': {
170 'standard': {
171 'format':
172 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
173 'datefmt': '%Y-%m-%d %H:%M:%S',
174 },
175 },
176 'handlers': {
177 'default': {
178 'class': 'logging.StreamHandler',
179 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700180 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700181 },
182 },
183 'loggers': {
184 '': {
185 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700186 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700187 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700188 }
189 }
190 }
191 env_config = os.getenv('LOGGING_CONFIG')
192 if env_config:
193 with open(env_config) as f:
194 config = json.load(f)
195 else:
196 config = DEFAULT_LOGGING_CONFIG
197
198 # Increase the logging level for verbose mode.
199 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700200 config = copy.deepcopy(config)
201 config['handlers']['default']['level'] = 'INFO'
202
203 if OPTIONS.logfile:
204 config = copy.deepcopy(config)
205 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400206 'class': 'logging.FileHandler',
207 'formatter': 'standard',
208 'level': 'INFO',
209 'mode': 'w',
210 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700211 }
212 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700213
214 logging.config.dictConfig(config)
215
216
Yifan Hong8e332ff2020-07-29 17:51:55 -0700217def SetHostToolLocation(tool_name, location):
218 OPTIONS.host_tools[tool_name] = location
219
220
Tao Bao39451582017-05-04 11:10:47 -0700221def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700222 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700223
Tao Bao73dd4f42018-10-04 16:25:33 -0700224 Args:
225 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700226 verbose: Whether the commands should be shown. Default to the global
227 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700228 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
229 stdin, etc. stdout and stderr will default to subprocess.PIPE and
230 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800231 universal_newlines will default to True, as most of the users in
232 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700233
234 Returns:
235 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700236 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700237 if 'stdout' not in kwargs and 'stderr' not in kwargs:
238 kwargs['stdout'] = subprocess.PIPE
239 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800240 if 'universal_newlines' not in kwargs:
241 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700242
243 # If explicitly set host tool location before, use that location to avoid
244 # PATH violation. Make a copy of args in case client relies on the content
245 # of args later.
246 if args and args[0] in OPTIONS.host_tools:
247 args = args[:]
248 args[0] = OPTIONS.host_tools[args[0]]
249
Tao Bao32fcdab2018-10-12 10:30:39 -0700250 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400251 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700252 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700253 return subprocess.Popen(args, **kwargs)
254
255
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800256def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800257 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800258
259 Args:
260 args: The command represented as a list of strings.
261 verbose: Whether the commands should be shown. Default to the global
262 verbosity if unspecified.
263 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
264 stdin, etc. stdout and stderr will default to subprocess.PIPE and
265 subprocess.STDOUT respectively unless caller specifies any of them.
266
Bill Peckham889b0c62019-02-21 18:53:37 -0800267 Raises:
268 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800269 """
270 proc = Run(args, verbose=verbose, **kwargs)
271 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800272
273 if proc.returncode != 0:
274 raise ExternalError(
275 "Failed to run command '{}' (exit code {})".format(
276 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800277
278
Tao Bao986ee862018-10-04 15:46:16 -0700279def RunAndCheckOutput(args, verbose=None, **kwargs):
280 """Runs the given command and returns the output.
281
282 Args:
283 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 verbose: Whether the commands should be shown. Default to the global
285 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700286 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
287 stdin, etc. stdout and stderr will default to subprocess.PIPE and
288 subprocess.STDOUT respectively unless caller specifies any of them.
289
290 Returns:
291 The output string.
292
293 Raises:
294 ExternalError: On non-zero exit from the command.
295 """
Tao Bao986ee862018-10-04 15:46:16 -0700296 proc = Run(args, verbose=verbose, **kwargs)
297 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800298 if output is None:
299 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700300 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400301 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700302 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700303 if proc.returncode != 0:
304 raise ExternalError(
305 "Failed to run command '{}' (exit code {}):\n{}".format(
306 args, proc.returncode, output))
307 return output
308
309
Tao Baoc765cca2018-01-31 17:32:40 -0800310def RoundUpTo4K(value):
311 rounded_up = value + 4095
312 return rounded_up - (rounded_up % 4096)
313
314
Ying Wang7e6d4e42010-12-13 16:25:36 -0800315def CloseInheritedPipes():
316 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
317 before doing other work."""
318 if platform.system() != "Darwin":
319 return
320 for d in range(3, 1025):
321 try:
322 stat = os.fstat(d)
323 if stat is not None:
324 pipebit = stat[0] & 0x1000
325 if pipebit != 0:
326 os.close(d)
327 except OSError:
328 pass
329
330
Tao Bao1c320f82019-10-04 23:25:12 -0700331class BuildInfo(object):
332 """A class that holds the information for a given build.
333
334 This class wraps up the property querying for a given source or target build.
335 It abstracts away the logic of handling OEM-specific properties, and caches
336 the commonly used properties such as fingerprint.
337
338 There are two types of info dicts: a) build-time info dict, which is generated
339 at build time (i.e. included in a target_files zip); b) OEM info dict that is
340 specified at package generation time (via command line argument
341 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
342 having "oem_fingerprint_properties" in build-time info dict), all the queries
343 would be answered based on build-time info dict only. Otherwise if using
344 OEM-specific properties, some of them will be calculated from two info dicts.
345
346 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800347 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700348
349 Attributes:
350 info_dict: The build-time info dict.
351 is_ab: Whether it's a build that uses A/B OTA.
352 oem_dicts: A list of OEM dicts.
353 oem_props: A list of OEM properties that should be read from OEM dicts; None
354 if the build doesn't use any OEM-specific property.
355 fingerprint: The fingerprint of the build, which would be calculated based
356 on OEM properties if applicable.
357 device: The device name, which could come from OEM dicts if applicable.
358 """
359
360 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
361 "ro.product.manufacturer", "ro.product.model",
362 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700363 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
364 "product", "odm", "vendor", "system_ext", "system"]
365 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
366 "product", "product_services", "odm", "vendor", "system"]
367 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700368
Tao Bao3ed35d32019-10-07 20:48:48 -0700369 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700370 """Initializes a BuildInfo instance with the given dicts.
371
372 Note that it only wraps up the given dicts, without making copies.
373
374 Arguments:
375 info_dict: The build-time info dict.
376 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
377 that it always uses the first dict to calculate the fingerprint or the
378 device name. The rest would be used for asserting OEM properties only
379 (e.g. one package can be installed on one of these devices).
380
381 Raises:
382 ValueError: On invalid inputs.
383 """
384 self.info_dict = info_dict
385 self.oem_dicts = oem_dicts
386
387 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700388
Hongguang Chend7c160f2020-05-03 21:24:26 -0700389 # Skip _oem_props if oem_dicts is None to use BuildInfo in
390 # sign_target_files_apks
391 if self.oem_dicts:
392 self._oem_props = info_dict.get("oem_fingerprint_properties")
393 else:
394 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700395
Daniel Normand5fe8622020-01-08 17:01:11 -0800396 def check_fingerprint(fingerprint):
397 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
398 raise ValueError(
399 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
400 "3.2.2. Build Parameters.".format(fingerprint))
401
Daniel Normand5fe8622020-01-08 17:01:11 -0800402 self._partition_fingerprints = {}
403 for partition in PARTITIONS_WITH_CARE_MAP:
404 try:
405 fingerprint = self.CalculatePartitionFingerprint(partition)
406 check_fingerprint(fingerprint)
407 self._partition_fingerprints[partition] = fingerprint
408 except ExternalError:
409 continue
410 if "system" in self._partition_fingerprints:
411 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
412 # need a fingerprint when creating the image.
413 self._partition_fingerprints[
414 "system_other"] = self._partition_fingerprints["system"]
415
Tao Bao1c320f82019-10-04 23:25:12 -0700416 # These two should be computed only after setting self._oem_props.
417 self._device = self.GetOemProperty("ro.product.device")
418 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800419 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700420
421 @property
422 def is_ab(self):
423 return self._is_ab
424
425 @property
426 def device(self):
427 return self._device
428
429 @property
430 def fingerprint(self):
431 return self._fingerprint
432
433 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700434 def oem_props(self):
435 return self._oem_props
436
437 def __getitem__(self, key):
438 return self.info_dict[key]
439
440 def __setitem__(self, key, value):
441 self.info_dict[key] = value
442
443 def get(self, key, default=None):
444 return self.info_dict.get(key, default)
445
446 def items(self):
447 return self.info_dict.items()
448
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000449 def _GetRawBuildProp(self, prop, partition):
450 prop_file = '{}.build.prop'.format(
451 partition) if partition else 'build.prop'
452 partition_props = self.info_dict.get(prop_file)
453 if not partition_props:
454 return None
455 return partition_props.GetProp(prop)
456
Daniel Normand5fe8622020-01-08 17:01:11 -0800457 def GetPartitionBuildProp(self, prop, partition):
458 """Returns the inquired build property for the provided partition."""
459 # If provided a partition for this property, only look within that
460 # partition's build.prop.
461 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
462 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
463 else:
464 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000465
466 prop_val = self._GetRawBuildProp(prop, partition)
467 if prop_val is not None:
468 return prop_val
469 raise ExternalError("couldn't find %s in %s.build.prop" %
470 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800471
Tao Bao1c320f82019-10-04 23:25:12 -0700472 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800473 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700474 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
475 return self._ResolveRoProductBuildProp(prop)
476
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000477 prop_val = self._GetRawBuildProp(prop, None)
478 if prop_val is not None:
479 return prop_val
480
481 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700482
483 def _ResolveRoProductBuildProp(self, prop):
484 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000485 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700486 if prop_val:
487 return prop_val
488
Steven Laver8e2086e2020-04-27 16:26:31 -0700489 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000490 source_order_val = self._GetRawBuildProp(
491 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700492 if source_order_val:
493 source_order = source_order_val.split(",")
494 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700495 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700496
497 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700498 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700499 raise ExternalError(
500 "Invalid ro.product.property_source_order '{}'".format(source_order))
501
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000502 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700503 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000504 "ro.product", "ro.product.{}".format(source_partition), 1)
505 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700506 if prop_val:
507 return prop_val
508
509 raise ExternalError("couldn't resolve {}".format(prop))
510
Steven Laver8e2086e2020-04-27 16:26:31 -0700511 def _GetRoProductPropsDefaultSourceOrder(self):
512 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
513 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000514 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700515 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000516 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700517 if android_version == "10":
518 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
519 # NOTE: float() conversion of android_version will have rounding error.
520 # We are checking for "9" or less, and using "< 10" is well outside of
521 # possible floating point rounding.
522 try:
523 android_version_val = float(android_version)
524 except ValueError:
525 android_version_val = 0
526 if android_version_val < 10:
527 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
528 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
529
Tao Bao1c320f82019-10-04 23:25:12 -0700530 def GetOemProperty(self, key):
531 if self.oem_props is not None and key in self.oem_props:
532 return self.oem_dicts[0][key]
533 return self.GetBuildProp(key)
534
Daniel Normand5fe8622020-01-08 17:01:11 -0800535 def GetPartitionFingerprint(self, partition):
536 return self._partition_fingerprints.get(partition, None)
537
538 def CalculatePartitionFingerprint(self, partition):
539 try:
540 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
541 except ExternalError:
542 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
543 self.GetPartitionBuildProp("ro.product.brand", partition),
544 self.GetPartitionBuildProp("ro.product.name", partition),
545 self.GetPartitionBuildProp("ro.product.device", partition),
546 self.GetPartitionBuildProp("ro.build.version.release", partition),
547 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400548 self.GetPartitionBuildProp(
549 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800550 self.GetPartitionBuildProp("ro.build.type", partition),
551 self.GetPartitionBuildProp("ro.build.tags", partition))
552
Tao Bao1c320f82019-10-04 23:25:12 -0700553 def CalculateFingerprint(self):
554 if self.oem_props is None:
555 try:
556 return self.GetBuildProp("ro.build.fingerprint")
557 except ExternalError:
558 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
559 self.GetBuildProp("ro.product.brand"),
560 self.GetBuildProp("ro.product.name"),
561 self.GetBuildProp("ro.product.device"),
562 self.GetBuildProp("ro.build.version.release"),
563 self.GetBuildProp("ro.build.id"),
564 self.GetBuildProp("ro.build.version.incremental"),
565 self.GetBuildProp("ro.build.type"),
566 self.GetBuildProp("ro.build.tags"))
567 return "%s/%s/%s:%s" % (
568 self.GetOemProperty("ro.product.brand"),
569 self.GetOemProperty("ro.product.name"),
570 self.GetOemProperty("ro.product.device"),
571 self.GetBuildProp("ro.build.thumbprint"))
572
573 def WriteMountOemScript(self, script):
574 assert self.oem_props is not None
575 recovery_mount_options = self.info_dict.get("recovery_mount_options")
576 script.Mount("/oem", recovery_mount_options)
577
578 def WriteDeviceAssertions(self, script, oem_no_mount):
579 # Read the property directly if not using OEM properties.
580 if not self.oem_props:
581 script.AssertDevice(self.device)
582 return
583
584 # Otherwise assert OEM properties.
585 if not self.oem_dicts:
586 raise ExternalError(
587 "No OEM file provided to answer expected assertions")
588
589 for prop in self.oem_props.split():
590 values = []
591 for oem_dict in self.oem_dicts:
592 if prop in oem_dict:
593 values.append(oem_dict[prop])
594 if not values:
595 raise ExternalError(
596 "The OEM file is missing the property %s" % (prop,))
597 script.AssertOemProperty(prop, values, oem_no_mount)
598
599
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000600def ReadFromInputFile(input_file, fn):
601 """Reads the contents of fn from input zipfile or directory."""
602 if isinstance(input_file, zipfile.ZipFile):
603 return input_file.read(fn).decode()
604 else:
605 path = os.path.join(input_file, *fn.split("/"))
606 try:
607 with open(path) as f:
608 return f.read()
609 except IOError as e:
610 if e.errno == errno.ENOENT:
611 raise KeyError(fn)
612
613
Tao Bao410ad8b2018-08-24 12:08:38 -0700614def LoadInfoDict(input_file, repacking=False):
615 """Loads the key/value pairs from the given input target_files.
616
Tianjiea85bdf02020-07-29 11:56:19 -0700617 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700618 checks and returns the parsed key/value pairs for to the given build. It's
619 usually called early when working on input target_files files, e.g. when
620 generating OTAs, or signing builds. Note that the function may be called
621 against an old target_files file (i.e. from past dessert releases). So the
622 property parsing needs to be backward compatible.
623
624 In a `META/misc_info.txt`, a few properties are stored as links to the files
625 in the PRODUCT_OUT directory. It works fine with the build system. However,
626 they are no longer available when (re)generating images from target_files zip.
627 When `repacking` is True, redirect these properties to the actual files in the
628 unzipped directory.
629
630 Args:
631 input_file: The input target_files file, which could be an open
632 zipfile.ZipFile instance, or a str for the dir that contains the files
633 unzipped from a target_files file.
634 repacking: Whether it's trying repack an target_files file after loading the
635 info dict (default: False). If so, it will rewrite a few loaded
636 properties (e.g. selinux_fc, root_dir) to point to the actual files in
637 target_files file. When doing repacking, `input_file` must be a dir.
638
639 Returns:
640 A dict that contains the parsed key/value pairs.
641
642 Raises:
643 AssertionError: On invalid input arguments.
644 ValueError: On malformed input values.
645 """
646 if repacking:
647 assert isinstance(input_file, str), \
648 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700649
Doug Zongkerc9253822014-02-04 12:17:58 -0800650 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000651 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800652
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700653 try:
Michael Runge6e836112014-04-15 17:40:21 -0700654 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700655 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700656 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700657
Tao Bao410ad8b2018-08-24 12:08:38 -0700658 if "recovery_api_version" not in d:
659 raise ValueError("Failed to find 'recovery_api_version'")
660 if "fstab_version" not in d:
661 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800662
Tao Bao410ad8b2018-08-24 12:08:38 -0700663 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700664 # "selinux_fc" properties should point to the file_contexts files
665 # (file_contexts.bin) under META/.
666 for key in d:
667 if key.endswith("selinux_fc"):
668 fc_basename = os.path.basename(d[key])
669 fc_config = os.path.join(input_file, "META", fc_basename)
670 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700671
Daniel Norman72c626f2019-05-13 15:58:14 -0700672 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700673
Tom Cherryd14b8952018-08-09 14:26:00 -0700674 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700675 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700676 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700677 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700678
David Anderson0ec64ac2019-12-06 12:21:18 -0800679 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700680 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700681 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800682 key_name = part_name + "_base_fs_file"
683 if key_name not in d:
684 continue
685 basename = os.path.basename(d[key_name])
686 base_fs_file = os.path.join(input_file, "META", basename)
687 if os.path.exists(base_fs_file):
688 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700689 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700690 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800691 "Failed to find %s base fs file: %s", part_name, base_fs_file)
692 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700693
Doug Zongker37974732010-09-16 17:44:38 -0700694 def makeint(key):
695 if key in d:
696 d[key] = int(d[key], 0)
697
698 makeint("recovery_api_version")
699 makeint("blocksize")
700 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700701 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700702 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700703 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700704 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800705 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700706
Steve Muckle903a1ca2020-05-07 17:32:10 -0700707 boot_images = "boot.img"
708 if "boot_images" in d:
709 boot_images = d["boot_images"]
710 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400711 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700712
Tao Bao765668f2019-10-04 22:03:00 -0700713 # Load recovery fstab if applicable.
714 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800715
Tianjie Xu861f4132018-09-12 11:49:33 -0700716 # Tries to load the build props for all partitions with care_map, including
717 # system and vendor.
718 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800719 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000720 d[partition_prop] = PartitionBuildProps.FromInputFile(
721 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700722 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800723
Tao Bao3ed35d32019-10-07 20:48:48 -0700724 # Set up the salt (based on fingerprint) that will be used when adding AVB
725 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800726 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700727 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800728 for partition in PARTITIONS_WITH_CARE_MAP:
729 fingerprint = build_info.GetPartitionFingerprint(partition)
730 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400731 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800732
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700733 return d
734
Tao Baod1de6f32017-03-01 16:38:48 -0800735
Daniel Norman4cc9df62019-07-18 10:11:07 -0700736def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900737 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700738 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900739
Daniel Norman4cc9df62019-07-18 10:11:07 -0700740
741def LoadDictionaryFromFile(file_path):
742 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900743 return LoadDictionaryFromLines(lines)
744
745
Michael Runge6e836112014-04-15 17:40:21 -0700746def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700747 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700748 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700749 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700750 if not line or line.startswith("#"):
751 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700752 if "=" in line:
753 name, value = line.split("=", 1)
754 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700755 return d
756
Tao Baod1de6f32017-03-01 16:38:48 -0800757
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000758class PartitionBuildProps(object):
759 """The class holds the build prop of a particular partition.
760
761 This class loads the build.prop and holds the build properties for a given
762 partition. It also partially recognizes the 'import' statement in the
763 build.prop; and calculates alternative values of some specific build
764 properties during runtime.
765
766 Attributes:
767 input_file: a zipped target-file or an unzipped target-file directory.
768 partition: name of the partition.
769 props_allow_override: a list of build properties to search for the
770 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000771 build_props: a dict of build properties for the given partition.
772 prop_overrides: a set of props that are overridden by import.
773 placeholder_values: A dict of runtime variables' values to replace the
774 placeholders in the build.prop file. We expect exactly one value for
775 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000776 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400777
Tianjie Xu9afb2212020-05-10 21:48:15 +0000778 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000779 self.input_file = input_file
780 self.partition = name
781 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000782 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000783 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000784 self.prop_overrides = set()
785 self.placeholder_values = {}
786 if placeholder_values:
787 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000788
789 @staticmethod
790 def FromDictionary(name, build_props):
791 """Constructs an instance from a build prop dictionary."""
792
793 props = PartitionBuildProps("unknown", name)
794 props.build_props = build_props.copy()
795 return props
796
797 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000798 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000799 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000800 data = ''
801 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
802 '{}/build.prop'.format(name.upper())]:
803 try:
804 data = ReadFromInputFile(input_file, prop_file)
805 break
806 except KeyError:
807 logger.warning('Failed to read %s', prop_file)
808
Tianjie Xu9afb2212020-05-10 21:48:15 +0000809 props = PartitionBuildProps(input_file, name, placeholder_values)
810 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000811 return props
812
Tianjie Xu9afb2212020-05-10 21:48:15 +0000813 def _LoadBuildProp(self, data):
814 for line in data.split('\n'):
815 line = line.strip()
816 if not line or line.startswith("#"):
817 continue
818 if line.startswith("import"):
819 overrides = self._ImportParser(line)
820 duplicates = self.prop_overrides.intersection(overrides.keys())
821 if duplicates:
822 raise ValueError('prop {} is overridden multiple times'.format(
823 ','.join(duplicates)))
824 self.prop_overrides = self.prop_overrides.union(overrides.keys())
825 self.build_props.update(overrides)
826 elif "=" in line:
827 name, value = line.split("=", 1)
828 if name in self.prop_overrides:
829 raise ValueError('prop {} is set again after overridden by import '
830 'statement'.format(name))
831 self.build_props[name] = value
832
833 def _ImportParser(self, line):
834 """Parses the build prop in a given import statement."""
835
836 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400837 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000838 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700839
840 if len(tokens) == 3:
841 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
842 return {}
843
Tianjie Xu9afb2212020-05-10 21:48:15 +0000844 import_path = tokens[1]
845 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
846 raise ValueError('Unrecognized import path {}'.format(line))
847
848 # We only recognize a subset of import statement that the init process
849 # supports. And we can loose the restriction based on how the dynamic
850 # fingerprint is used in practice. The placeholder format should be
851 # ${placeholder}, and its value should be provided by the caller through
852 # the placeholder_values.
853 for prop, value in self.placeholder_values.items():
854 prop_place_holder = '${{{}}}'.format(prop)
855 if prop_place_holder in import_path:
856 import_path = import_path.replace(prop_place_holder, value)
857 if '$' in import_path:
858 logger.info('Unresolved place holder in import path %s', import_path)
859 return {}
860
861 import_path = import_path.replace('/{}'.format(self.partition),
862 self.partition.upper())
863 logger.info('Parsing build props override from %s', import_path)
864
865 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
866 d = LoadDictionaryFromLines(lines)
867 return {key: val for key, val in d.items()
868 if key in self.props_allow_override}
869
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000870 def GetProp(self, prop):
871 return self.build_props.get(prop)
872
873
Tianjie Xucfa86222016-03-07 16:31:19 -0800874def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
875 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700876 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700877 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700878 self.mount_point = mount_point
879 self.fs_type = fs_type
880 self.device = device
881 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700882 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700883 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700884
885 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800886 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700887 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700888 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700889 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700890
Tao Baod1de6f32017-03-01 16:38:48 -0800891 assert fstab_version == 2
892
893 d = {}
894 for line in data.split("\n"):
895 line = line.strip()
896 if not line or line.startswith("#"):
897 continue
898
899 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
900 pieces = line.split()
901 if len(pieces) != 5:
902 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
903
904 # Ignore entries that are managed by vold.
905 options = pieces[4]
906 if "voldmanaged=" in options:
907 continue
908
909 # It's a good line, parse it.
910 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700911 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800912 options = options.split(",")
913 for i in options:
914 if i.startswith("length="):
915 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700916 elif i == "slotselect":
917 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800918 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800919 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700920 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800921
Tao Baod1de6f32017-03-01 16:38:48 -0800922 mount_flags = pieces[3]
923 # Honor the SELinux context if present.
924 context = None
925 for i in mount_flags.split(","):
926 if i.startswith("context="):
927 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800928
Tao Baod1de6f32017-03-01 16:38:48 -0800929 mount_point = pieces[1]
930 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700931 device=pieces[0], length=length, context=context,
932 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800933
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700934 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700935 # system. Other areas assume system is always at "/system" so point /system
936 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700937 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800938 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700939 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700940 return d
941
942
Tao Bao765668f2019-10-04 22:03:00 -0700943def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
944 """Finds the path to recovery fstab and loads its contents."""
945 # recovery fstab is only meaningful when installing an update via recovery
946 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -0700947 if info_dict.get('ab_update') == 'true' and \
948 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -0700949 return None
950
951 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
952 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
953 # cases, since it may load the info_dict from an old build (e.g. when
954 # generating incremental OTAs from that build).
955 system_root_image = info_dict.get('system_root_image') == 'true'
956 if info_dict.get('no_recovery') != 'true':
957 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
958 if isinstance(input_file, zipfile.ZipFile):
959 if recovery_fstab_path not in input_file.namelist():
960 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
961 else:
962 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
963 if not os.path.exists(path):
964 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
965 return LoadRecoveryFSTab(
966 read_helper, info_dict['fstab_version'], recovery_fstab_path,
967 system_root_image)
968
969 if info_dict.get('recovery_as_boot') == 'true':
970 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
971 if isinstance(input_file, zipfile.ZipFile):
972 if recovery_fstab_path not in input_file.namelist():
973 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
974 else:
975 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
976 if not os.path.exists(path):
977 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
978 return LoadRecoveryFSTab(
979 read_helper, info_dict['fstab_version'], recovery_fstab_path,
980 system_root_image)
981
982 return None
983
984
Doug Zongker37974732010-09-16 17:44:38 -0700985def DumpInfoDict(d):
986 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700987 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700988
Dan Albert8b72aef2015-03-23 19:13:21 -0700989
Daniel Norman55417142019-11-25 16:04:36 -0800990def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700991 """Merges dynamic partition info variables.
992
993 Args:
994 framework_dict: The dictionary of dynamic partition info variables from the
995 partial framework target files.
996 vendor_dict: The dictionary of dynamic partition info variables from the
997 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700998
999 Returns:
1000 The merged dynamic partition info dictionary.
1001 """
1002 merged_dict = {}
1003 # Partition groups and group sizes are defined by the vendor dict because
1004 # these values may vary for each board that uses a shared system image.
1005 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -08001006 framework_dynamic_partition_list = framework_dict.get(
1007 "dynamic_partition_list", "")
1008 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
1009 merged_dict["dynamic_partition_list"] = ("%s %s" % (
1010 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001011 for partition_group in merged_dict["super_partition_groups"].split(" "):
1012 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001013 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001014 if key not in vendor_dict:
1015 raise ValueError("Vendor dict does not contain required key %s." % key)
1016 merged_dict[key] = vendor_dict[key]
1017
1018 # Set the partition group's partition list using a concatenation of the
1019 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001020 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001021 merged_dict[key] = (
1022 "%s %s" %
1023 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301024
1025 # Pick virtual ab related flags from vendor dict, if defined.
1026 if "virtual_ab" in vendor_dict.keys():
Kelvin Zhang0876c412020-06-23 15:06:58 -04001027 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301028 if "virtual_ab_retrofit" in vendor_dict.keys():
Kelvin Zhang0876c412020-06-23 15:06:58 -04001029 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001030 return merged_dict
1031
1032
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001033def AppendAVBSigningArgs(cmd, partition):
1034 """Append signing arguments for avbtool."""
1035 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1036 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001037 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1038 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1039 if os.path.exists(new_key_path):
1040 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001041 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1042 if key_path and algorithm:
1043 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001044 avb_salt = OPTIONS.info_dict.get("avb_salt")
1045 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001046 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001047 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001048
1049
Tao Bao765668f2019-10-04 22:03:00 -07001050def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001051 """Returns the VBMeta arguments for partition.
1052
1053 It sets up the VBMeta argument by including the partition descriptor from the
1054 given 'image', or by configuring the partition as a chained partition.
1055
1056 Args:
1057 partition: The name of the partition (e.g. "system").
1058 image: The path to the partition image.
1059 info_dict: A dict returned by common.LoadInfoDict(). Will use
1060 OPTIONS.info_dict if None has been given.
1061
1062 Returns:
1063 A list of VBMeta arguments.
1064 """
1065 if info_dict is None:
1066 info_dict = OPTIONS.info_dict
1067
1068 # Check if chain partition is used.
1069 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001070 if not key_path:
1071 return ["--include_descriptors_from_image", image]
1072
1073 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1074 # into vbmeta.img. The recovery image will be configured on an independent
1075 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1076 # See details at
1077 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001078 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001079 return []
1080
1081 # Otherwise chain the partition into vbmeta.
1082 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1083 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001084
1085
Tao Bao02a08592018-07-22 12:40:45 -07001086def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1087 """Constructs and returns the arg to build or verify a chained partition.
1088
1089 Args:
1090 partition: The partition name.
1091 info_dict: The info dict to look up the key info and rollback index
1092 location.
1093 key: The key to be used for building or verifying the partition. Defaults to
1094 the key listed in info_dict.
1095
1096 Returns:
1097 A string of form "partition:rollback_index_location:key" that can be used to
1098 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001099 """
1100 if key is None:
1101 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001102 if key and not os.path.exists(key) and OPTIONS.search_path:
1103 new_key_path = os.path.join(OPTIONS.search_path, key)
1104 if os.path.exists(new_key_path):
1105 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001106 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001107 rollback_index_location = info_dict[
1108 "avb_" + partition + "_rollback_index_location"]
1109 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1110
1111
Tianjie20dd8f22020-04-19 15:51:16 -07001112def ConstructAftlMakeImageCommands(output_image):
1113 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001114
1115 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001116 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001117 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1118 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1119 'No AFTL manufacturer key provided.'
1120
1121 vbmeta_image = MakeTempFile()
1122 os.rename(output_image, vbmeta_image)
1123 build_info = BuildInfo(OPTIONS.info_dict)
1124 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001125 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001126 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001127 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001128 "--vbmeta_image_path", vbmeta_image,
1129 "--output", output_image,
1130 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001131 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001132 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1133 "--algorithm", "SHA256_RSA4096",
1134 "--padding", "4096"]
1135 if OPTIONS.aftl_signer_helper:
1136 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001137 return aftl_cmd
1138
1139
1140def AddAftlInclusionProof(output_image):
1141 """Appends the aftl inclusion proof to the vbmeta image."""
1142
1143 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001144 RunAndCheckOutput(aftl_cmd)
1145
1146 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1147 output_image, '--transparency_log_pub_keys',
1148 OPTIONS.aftl_key_path]
1149 RunAndCheckOutput(verify_cmd)
1150
1151
Daniel Norman276f0622019-07-26 14:13:51 -07001152def BuildVBMeta(image_path, partitions, name, needed_partitions):
1153 """Creates a VBMeta image.
1154
1155 It generates the requested VBMeta image. The requested image could be for
1156 top-level or chained VBMeta image, which is determined based on the name.
1157
1158 Args:
1159 image_path: The output path for the new VBMeta image.
1160 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001161 values. Only valid partition names are accepted, as partitions listed
1162 in common.AVB_PARTITIONS and custom partitions listed in
1163 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001164 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1165 needed_partitions: Partitions whose descriptors should be included into the
1166 generated VBMeta image.
1167
1168 Raises:
1169 AssertionError: On invalid input args.
1170 """
1171 avbtool = OPTIONS.info_dict["avb_avbtool"]
1172 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1173 AppendAVBSigningArgs(cmd, name)
1174
Hongguang Chenf23364d2020-04-27 18:36:36 -07001175 custom_partitions = OPTIONS.info_dict.get(
1176 "avb_custom_images_partition_list", "").strip().split()
1177
Daniel Norman276f0622019-07-26 14:13:51 -07001178 for partition, path in partitions.items():
1179 if partition not in needed_partitions:
1180 continue
1181 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001182 partition in AVB_VBMETA_PARTITIONS or
1183 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001184 'Unknown partition: {}'.format(partition)
1185 assert os.path.exists(path), \
1186 'Failed to find {} for {}'.format(path, partition)
1187 cmd.extend(GetAvbPartitionArg(partition, path))
1188
1189 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1190 if args and args.strip():
1191 split_args = shlex.split(args)
1192 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001193 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001194 # as a path relative to source tree, which may not be available at the
1195 # same location when running this script (we have the input target_files
1196 # zip only). For such cases, we additionally scan other locations (e.g.
1197 # IMAGES/, RADIO/, etc) before bailing out.
1198 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001199 chained_image = split_args[index + 1]
1200 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001201 continue
1202 found = False
1203 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1204 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001205 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001206 if os.path.exists(alt_path):
1207 split_args[index + 1] = alt_path
1208 found = True
1209 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001210 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001211 cmd.extend(split_args)
1212
1213 RunAndCheckOutput(cmd)
1214
Tianjie Xueaed60c2020-03-12 00:33:28 -07001215 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001216 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001217 AddAftlInclusionProof(image_path)
1218
Daniel Norman276f0622019-07-26 14:13:51 -07001219
J. Avila98cd4cc2020-06-10 20:09:10 +00001220def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001221 ramdisk_img = tempfile.NamedTemporaryFile()
1222
1223 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1224 cmd = ["mkbootfs", "-f", fs_config_file,
1225 os.path.join(sourcedir, "RAMDISK")]
1226 else:
1227 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1228 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001229 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001230 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001231 stdout=ramdisk_img.file.fileno())
1232 else:
1233 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001234
1235 p2.wait()
1236 p1.wait()
1237 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001238 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001239
1240 return ramdisk_img
1241
1242
Steve Muckle9793cf62020-04-08 18:27:00 -07001243def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001244 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001245 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001246
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001247 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001248 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1249 we are building a two-step special image (i.e. building a recovery image to
1250 be loaded into /boot in two-step OTAs).
1251
1252 Return the image data, or None if sourcedir does not appear to contains files
1253 for building the requested image.
1254 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001255
Steve Muckle9793cf62020-04-08 18:27:00 -07001256 # "boot" or "recovery", without extension.
1257 partition_name = os.path.basename(sourcedir).lower()
1258
1259 if partition_name == "recovery":
1260 kernel = "kernel"
1261 else:
1262 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001263 kernel = kernel.replace(".img", "")
Steve Muckle9793cf62020-04-08 18:27:00 -07001264 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001265 return None
1266
1267 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001268 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001269
Doug Zongkerd5131602012-08-02 14:46:42 -07001270 if info_dict is None:
1271 info_dict = OPTIONS.info_dict
1272
Doug Zongkereef39442009-04-02 12:14:19 -07001273 img = tempfile.NamedTemporaryFile()
1274
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001275 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001276 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1277 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001278
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001279 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1280 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1281
Steve Muckle9793cf62020-04-08 18:27:00 -07001282 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001283
Benoit Fradina45a8682014-07-14 21:00:43 +02001284 fn = os.path.join(sourcedir, "second")
1285 if os.access(fn, os.F_OK):
1286 cmd.append("--second")
1287 cmd.append(fn)
1288
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001289 fn = os.path.join(sourcedir, "dtb")
1290 if os.access(fn, os.F_OK):
1291 cmd.append("--dtb")
1292 cmd.append(fn)
1293
Doug Zongker171f1cd2009-06-15 22:36:37 -07001294 fn = os.path.join(sourcedir, "cmdline")
1295 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001296 cmd.append("--cmdline")
1297 cmd.append(open(fn).read().rstrip("\n"))
1298
1299 fn = os.path.join(sourcedir, "base")
1300 if os.access(fn, os.F_OK):
1301 cmd.append("--base")
1302 cmd.append(open(fn).read().rstrip("\n"))
1303
Ying Wang4de6b5b2010-08-25 14:29:34 -07001304 fn = os.path.join(sourcedir, "pagesize")
1305 if os.access(fn, os.F_OK):
1306 cmd.append("--pagesize")
1307 cmd.append(open(fn).read().rstrip("\n"))
1308
Steve Mucklef84668e2020-03-16 19:13:46 -07001309 if partition_name == "recovery":
1310 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301311 if not args:
1312 # Fall back to "mkbootimg_args" for recovery image
1313 # in case "recovery_mkbootimg_args" is not set.
1314 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001315 else:
1316 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001317 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001318 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001319
Tao Bao76def242017-11-21 09:25:31 -08001320 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001321 if args and args.strip():
1322 cmd.extend(shlex.split(args))
1323
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001324 if has_ramdisk:
1325 cmd.extend(["--ramdisk", ramdisk_img.name])
1326
Tao Baod95e9fd2015-03-29 23:07:41 -07001327 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001328 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001329 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001330 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001331 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001332 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001333
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001334 if partition_name == "recovery":
1335 if info_dict.get("include_recovery_dtbo") == "true":
1336 fn = os.path.join(sourcedir, "recovery_dtbo")
1337 cmd.extend(["--recovery_dtbo", fn])
1338 if info_dict.get("include_recovery_acpio") == "true":
1339 fn = os.path.join(sourcedir, "recovery_acpio")
1340 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001341
Tao Bao986ee862018-10-04 15:46:16 -07001342 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001343
Tao Bao76def242017-11-21 09:25:31 -08001344 if (info_dict.get("boot_signer") == "true" and
1345 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001346 # Hard-code the path as "/boot" for two-step special recovery image (which
1347 # will be loaded into /boot during the two-step OTA).
1348 if two_step_image:
1349 path = "/boot"
1350 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001351 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001352 cmd = [OPTIONS.boot_signer_path]
1353 cmd.extend(OPTIONS.boot_signer_args)
1354 cmd.extend([path, img.name,
1355 info_dict["verity_key"] + ".pk8",
1356 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001357 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001358
Tao Baod95e9fd2015-03-29 23:07:41 -07001359 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001360 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001361 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001362 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001363 # We have switched from the prebuilt futility binary to using the tool
1364 # (futility-host) built from the source. Override the setting in the old
1365 # TF.zip.
1366 futility = info_dict["futility"]
1367 if futility.startswith("prebuilts/"):
1368 futility = "futility-host"
1369 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001370 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001371 info_dict["vboot_key"] + ".vbprivk",
1372 info_dict["vboot_subkey"] + ".vbprivk",
1373 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001374 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001375 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001376
Tao Baof3282b42015-04-01 11:21:55 -07001377 # Clean up the temp files.
1378 img_unsigned.close()
1379 img_keyblock.close()
1380
David Zeuthen8fecb282017-12-01 16:24:01 -05001381 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001382 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001383 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001384 if partition_name == "recovery":
1385 part_size = info_dict["recovery_size"]
1386 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001387 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001388 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001389 "--partition_size", str(part_size), "--partition_name",
1390 partition_name]
1391 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001392 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001393 if args and args.strip():
1394 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001395 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001396
1397 img.seek(os.SEEK_SET, 0)
1398 data = img.read()
1399
1400 if has_ramdisk:
1401 ramdisk_img.close()
1402 img.close()
1403
1404 return data
1405
1406
Doug Zongkerd5131602012-08-02 14:46:42 -07001407def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001408 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001409 """Return a File object with the desired bootable image.
1410
1411 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1412 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1413 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001414
Doug Zongker55d93282011-01-25 17:03:34 -08001415 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1416 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001417 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001418 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001419
1420 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1421 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001422 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001423 return File.FromLocalFile(name, prebuilt_path)
1424
Tao Bao32fcdab2018-10-12 10:30:39 -07001425 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001426
1427 if info_dict is None:
1428 info_dict = OPTIONS.info_dict
1429
1430 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001431 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1432 # for recovery.
1433 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1434 prebuilt_name != "boot.img" or
1435 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001436
Doug Zongker6f1d0312014-08-22 08:07:12 -07001437 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001438 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001439 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001440 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001441 if data:
1442 return File(name, data)
1443 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001444
Doug Zongkereef39442009-04-02 12:14:19 -07001445
Steve Mucklee1b10862019-07-10 10:49:37 -07001446def _BuildVendorBootImage(sourcedir, info_dict=None):
1447 """Build a vendor boot image from the specified sourcedir.
1448
1449 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1450 turn them into a vendor boot image.
1451
1452 Return the image data, or None if sourcedir does not appear to contains files
1453 for building the requested image.
1454 """
1455
1456 if info_dict is None:
1457 info_dict = OPTIONS.info_dict
1458
1459 img = tempfile.NamedTemporaryFile()
1460
J. Avila98cd4cc2020-06-10 20:09:10 +00001461 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1462 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001463
1464 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1465 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1466
1467 cmd = [mkbootimg]
1468
1469 fn = os.path.join(sourcedir, "dtb")
1470 if os.access(fn, os.F_OK):
1471 cmd.append("--dtb")
1472 cmd.append(fn)
1473
1474 fn = os.path.join(sourcedir, "vendor_cmdline")
1475 if os.access(fn, os.F_OK):
1476 cmd.append("--vendor_cmdline")
1477 cmd.append(open(fn).read().rstrip("\n"))
1478
1479 fn = os.path.join(sourcedir, "base")
1480 if os.access(fn, os.F_OK):
1481 cmd.append("--base")
1482 cmd.append(open(fn).read().rstrip("\n"))
1483
1484 fn = os.path.join(sourcedir, "pagesize")
1485 if os.access(fn, os.F_OK):
1486 cmd.append("--pagesize")
1487 cmd.append(open(fn).read().rstrip("\n"))
1488
1489 args = info_dict.get("mkbootimg_args")
1490 if args and args.strip():
1491 cmd.extend(shlex.split(args))
1492
1493 args = info_dict.get("mkbootimg_version_args")
1494 if args and args.strip():
1495 cmd.extend(shlex.split(args))
1496
1497 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1498 cmd.extend(["--vendor_boot", img.name])
1499
1500 RunAndCheckOutput(cmd)
1501
1502 # AVB: if enabled, calculate and add hash.
1503 if info_dict.get("avb_enable") == "true":
1504 avbtool = info_dict["avb_avbtool"]
1505 part_size = info_dict["vendor_boot_size"]
1506 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001507 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001508 AppendAVBSigningArgs(cmd, "vendor_boot")
1509 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1510 if args and args.strip():
1511 cmd.extend(shlex.split(args))
1512 RunAndCheckOutput(cmd)
1513
1514 img.seek(os.SEEK_SET, 0)
1515 data = img.read()
1516
1517 ramdisk_img.close()
1518 img.close()
1519
1520 return data
1521
1522
1523def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1524 info_dict=None):
1525 """Return a File object with the desired vendor boot image.
1526
1527 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1528 the source files in 'unpack_dir'/'tree_subdir'."""
1529
1530 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1531 if os.path.exists(prebuilt_path):
1532 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1533 return File.FromLocalFile(name, prebuilt_path)
1534
1535 logger.info("building image from target_files %s...", tree_subdir)
1536
1537 if info_dict is None:
1538 info_dict = OPTIONS.info_dict
1539
Kelvin Zhang0876c412020-06-23 15:06:58 -04001540 data = _BuildVendorBootImage(
1541 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001542 if data:
1543 return File(name, data)
1544 return None
1545
1546
Narayan Kamatha07bf042017-08-14 14:49:21 +01001547def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001548 """Gunzips the given gzip compressed file to a given output file."""
1549 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001550 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001551 shutil.copyfileobj(in_file, out_file)
1552
1553
Tao Bao0ff15de2019-03-20 11:26:06 -07001554def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001555 """Unzips the archive to the given directory.
1556
1557 Args:
1558 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001559 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001560 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1561 archvie. Non-matching patterns will be filtered out. If there's no match
1562 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001563 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001564 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001565 if patterns is not None:
1566 # Filter out non-matching patterns. unzip will complain otherwise.
1567 with zipfile.ZipFile(filename) as input_zip:
1568 names = input_zip.namelist()
1569 filtered = [
1570 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1571
1572 # There isn't any matching files. Don't unzip anything.
1573 if not filtered:
1574 return
1575 cmd.extend(filtered)
1576
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001577 RunAndCheckOutput(cmd)
1578
1579
Doug Zongker75f17362009-12-08 13:46:44 -08001580def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001581 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001582
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001583 Args:
1584 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1585 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1586
1587 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1588 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001589
Tao Bao1c830bf2017-12-25 10:43:47 -08001590 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001591 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001592 """
Doug Zongkereef39442009-04-02 12:14:19 -07001593
Tao Bao1c830bf2017-12-25 10:43:47 -08001594 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001595 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1596 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001597 UnzipToDir(m.group(1), tmp, pattern)
1598 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001599 filename = m.group(1)
1600 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001601 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001602
Tao Baodba59ee2018-01-09 13:21:02 -08001603 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001604
1605
Yifan Hong8a66a712019-04-04 15:37:57 -07001606def GetUserImage(which, tmpdir, input_zip,
1607 info_dict=None,
1608 allow_shared_blocks=None,
1609 hashtree_info_generator=None,
1610 reset_file_map=False):
1611 """Returns an Image object suitable for passing to BlockImageDiff.
1612
1613 This function loads the specified image from the given path. If the specified
1614 image is sparse, it also performs additional processing for OTA purpose. For
1615 example, it always adds block 0 to clobbered blocks list. It also detects
1616 files that cannot be reconstructed from the block list, for whom we should
1617 avoid applying imgdiff.
1618
1619 Args:
1620 which: The partition name.
1621 tmpdir: The directory that contains the prebuilt image and block map file.
1622 input_zip: The target-files ZIP archive.
1623 info_dict: The dict to be looked up for relevant info.
1624 allow_shared_blocks: If image is sparse, whether having shared blocks is
1625 allowed. If none, it is looked up from info_dict.
1626 hashtree_info_generator: If present and image is sparse, generates the
1627 hashtree_info for this sparse image.
1628 reset_file_map: If true and image is sparse, reset file map before returning
1629 the image.
1630 Returns:
1631 A Image object. If it is a sparse image and reset_file_map is False, the
1632 image will have file_map info loaded.
1633 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001634 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001635 info_dict = LoadInfoDict(input_zip)
1636
1637 is_sparse = info_dict.get("extfs_sparse_flag")
1638
1639 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1640 # shared blocks (i.e. some blocks will show up in multiple files' block
1641 # list). We can only allocate such shared blocks to the first "owner", and
1642 # disable imgdiff for all later occurrences.
1643 if allow_shared_blocks is None:
1644 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1645
1646 if is_sparse:
1647 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1648 hashtree_info_generator)
1649 if reset_file_map:
1650 img.ResetFileMap()
1651 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001652 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001653
1654
1655def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1656 """Returns a Image object suitable for passing to BlockImageDiff.
1657
1658 This function loads the specified non-sparse image from the given path.
1659
1660 Args:
1661 which: The partition name.
1662 tmpdir: The directory that contains the prebuilt image and block map file.
1663 Returns:
1664 A Image object.
1665 """
1666 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
Tianjie Xu41976c72019-07-03 13:57:01 -07001673 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1674
Yifan Hong8a66a712019-04-04 15:37:57 -07001675
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001676def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1677 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001678 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1679
1680 This function loads the specified sparse image from the given path, and
1681 performs additional processing for OTA purpose. For example, it always adds
1682 block 0 to clobbered blocks list. It also detects files that cannot be
1683 reconstructed from the block list, for whom we should avoid applying imgdiff.
1684
1685 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001686 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001687 tmpdir: The directory that contains the prebuilt image and block map file.
1688 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001689 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001690 hashtree_info_generator: If present, generates the hashtree_info for this
1691 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001692 Returns:
1693 A SparseImage object, with file_map info loaded.
1694 """
Tao Baoc765cca2018-01-31 17:32:40 -08001695 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1696 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1697
1698 # The image and map files must have been created prior to calling
1699 # ota_from_target_files.py (since LMP).
1700 assert os.path.exists(path) and os.path.exists(mappath)
1701
1702 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1703 # it to clobbered_blocks so that it will be written to the target
1704 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1705 clobbered_blocks = "0"
1706
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001707 image = sparse_img.SparseImage(
1708 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1709 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001710
1711 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1712 # if they contain all zeros. We can't reconstruct such a file from its block
1713 # list. Tag such entries accordingly. (Bug: 65213616)
1714 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001715 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001716 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001717 continue
1718
Tom Cherryd14b8952018-08-09 14:26:00 -07001719 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1720 # filename listed in system.map may contain an additional leading slash
1721 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1722 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001723 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001724
Tom Cherryd14b8952018-08-09 14:26:00 -07001725 # Special handling another case, where files not under /system
1726 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001727 if which == 'system' and not arcname.startswith('SYSTEM'):
1728 arcname = 'ROOT/' + arcname
1729
1730 assert arcname in input_zip.namelist(), \
1731 "Failed to find the ZIP entry for {}".format(entry)
1732
Tao Baoc765cca2018-01-31 17:32:40 -08001733 info = input_zip.getinfo(arcname)
1734 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001735
1736 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001737 # image, check the original block list to determine its completeness. Note
1738 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001739 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001740 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001741
Tao Baoc765cca2018-01-31 17:32:40 -08001742 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1743 ranges.extra['incomplete'] = True
1744
1745 return image
1746
1747
Doug Zongkereef39442009-04-02 12:14:19 -07001748def GetKeyPasswords(keylist):
1749 """Given a list of keys, prompt the user to enter passwords for
1750 those which require them. Return a {key: password} dict. password
1751 will be None if the key has no password."""
1752
Doug Zongker8ce7c252009-05-22 13:34:54 -07001753 no_passwords = []
1754 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001755 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001756 devnull = open("/dev/null", "w+b")
1757 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001758 # We don't need a password for things that aren't really keys.
1759 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001760 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001761 continue
1762
T.R. Fullhart37e10522013-03-18 10:31:26 -07001763 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001764 "-inform", "DER", "-nocrypt"],
1765 stdin=devnull.fileno(),
1766 stdout=devnull.fileno(),
1767 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001768 p.communicate()
1769 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001770 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001771 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001772 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001773 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1774 "-inform", "DER", "-passin", "pass:"],
1775 stdin=devnull.fileno(),
1776 stdout=devnull.fileno(),
1777 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001778 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001779 if p.returncode == 0:
1780 # Encrypted key with empty string as password.
1781 key_passwords[k] = ''
1782 elif stderr.startswith('Error decrypting key'):
1783 # Definitely encrypted key.
1784 # It would have said "Error reading key" if it didn't parse correctly.
1785 need_passwords.append(k)
1786 else:
1787 # Potentially, a type of key that openssl doesn't understand.
1788 # We'll let the routines in signapk.jar handle it.
1789 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001790 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001791
T.R. Fullhart37e10522013-03-18 10:31:26 -07001792 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001793 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001794 return key_passwords
1795
1796
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001797def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001798 """Gets the minSdkVersion declared in the APK.
1799
changho.shin0f125362019-07-08 10:59:00 +09001800 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001801 This can be both a decimal number (API Level) or a codename.
1802
1803 Args:
1804 apk_name: The APK filename.
1805
1806 Returns:
1807 The parsed SDK version string.
1808
1809 Raises:
1810 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001811 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001812 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001813 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001814 stderr=subprocess.PIPE)
1815 stdoutdata, stderrdata = proc.communicate()
1816 if proc.returncode != 0:
1817 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001818 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001819 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001820
Tao Baof47bf0f2018-03-21 23:28:51 -07001821 for line in stdoutdata.split("\n"):
1822 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001823 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1824 if m:
1825 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001826 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001827
1828
1829def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001830 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001831
Tao Baof47bf0f2018-03-21 23:28:51 -07001832 If minSdkVersion is set to a codename, it is translated to a number using the
1833 provided map.
1834
1835 Args:
1836 apk_name: The APK filename.
1837
1838 Returns:
1839 The parsed SDK version number.
1840
1841 Raises:
1842 ExternalError: On failing to get the min SDK version number.
1843 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001844 version = GetMinSdkVersion(apk_name)
1845 try:
1846 return int(version)
1847 except ValueError:
1848 # Not a decimal number. Codename?
1849 if version in codename_to_api_level_map:
1850 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04001851 raise ExternalError(
1852 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1853 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001854
1855
1856def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001857 codename_to_api_level_map=None, whole_file=False,
1858 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001859 """Sign the input_name zip/jar/apk, producing output_name. Use the
1860 given key and password (the latter may be None if the key does not
1861 have a password.
1862
Doug Zongker951495f2009-08-14 12:44:19 -07001863 If whole_file is true, use the "-w" option to SignApk to embed a
1864 signature that covers the whole file in the archive comment of the
1865 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001866
1867 min_api_level is the API Level (int) of the oldest platform this file may end
1868 up on. If not specified for an APK, the API Level is obtained by interpreting
1869 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1870
1871 codename_to_api_level_map is needed to translate the codename which may be
1872 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001873
1874 Caller may optionally specify extra args to be passed to SignApk, which
1875 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001876 """
Tao Bao76def242017-11-21 09:25:31 -08001877 if codename_to_api_level_map is None:
1878 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001879 if extra_signapk_args is None:
1880 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001881
Alex Klyubin9667b182015-12-10 13:38:50 -08001882 java_library_path = os.path.join(
1883 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1884
Tao Baoe95540e2016-11-08 12:08:53 -08001885 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1886 ["-Djava.library.path=" + java_library_path,
1887 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001888 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001889 if whole_file:
1890 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001891
1892 min_sdk_version = min_api_level
1893 if min_sdk_version is None:
1894 if not whole_file:
1895 min_sdk_version = GetMinSdkVersionInt(
1896 input_name, codename_to_api_level_map)
1897 if min_sdk_version is not None:
1898 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1899
T.R. Fullhart37e10522013-03-18 10:31:26 -07001900 cmd.extend([key + OPTIONS.public_key_suffix,
1901 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001902 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001903
Tao Bao73dd4f42018-10-04 16:25:33 -07001904 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001905 if password is not None:
1906 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001907 stdoutdata, _ = proc.communicate(password)
1908 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001909 raise ExternalError(
1910 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001911 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001912
Doug Zongkereef39442009-04-02 12:14:19 -07001913
Doug Zongker37974732010-09-16 17:44:38 -07001914def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001915 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001916
Tao Bao9dd909e2017-11-14 11:27:32 -08001917 For non-AVB images, raise exception if the data is too big. Print a warning
1918 if the data is nearing the maximum size.
1919
1920 For AVB images, the actual image size should be identical to the limit.
1921
1922 Args:
1923 data: A string that contains all the data for the partition.
1924 target: The partition name. The ".img" suffix is optional.
1925 info_dict: The dict to be looked up for relevant info.
1926 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001927 if target.endswith(".img"):
1928 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001929 mount_point = "/" + target
1930
Ying Wangf8824af2014-06-03 14:07:27 -07001931 fs_type = None
1932 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001933 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001934 if mount_point == "/userdata":
1935 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001936 p = info_dict["fstab"][mount_point]
1937 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001938 device = p.device
1939 if "/" in device:
1940 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001941 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001942 if not fs_type or not limit:
1943 return
Doug Zongkereef39442009-04-02 12:14:19 -07001944
Andrew Boie0f9aec82012-02-14 09:32:52 -08001945 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001946 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1947 # path.
1948 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1949 if size != limit:
1950 raise ExternalError(
1951 "Mismatching image size for %s: expected %d actual %d" % (
1952 target, limit, size))
1953 else:
1954 pct = float(size) * 100.0 / limit
1955 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1956 if pct >= 99.0:
1957 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04001958
1959 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001960 logger.warning("\n WARNING: %s\n", msg)
1961 else:
1962 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001963
1964
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001965def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001966 """Parses the APK certs info from a given target-files zip.
1967
1968 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1969 tuple with the following elements: (1) a dictionary that maps packages to
1970 certs (based on the "certificate" and "private_key" attributes in the file;
1971 (2) a string representing the extension of compressed APKs in the target files
1972 (e.g ".gz", ".bro").
1973
1974 Args:
1975 tf_zip: The input target_files ZipFile (already open).
1976
1977 Returns:
1978 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1979 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1980 no compressed APKs.
1981 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001982 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001983 compressed_extension = None
1984
Tao Bao0f990332017-09-08 19:02:54 -07001985 # META/apkcerts.txt contains the info for _all_ the packages known at build
1986 # time. Filter out the ones that are not installed.
1987 installed_files = set()
1988 for name in tf_zip.namelist():
1989 basename = os.path.basename(name)
1990 if basename:
1991 installed_files.add(basename)
1992
Tao Baoda30cfa2017-12-01 16:19:46 -08001993 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001994 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001995 if not line:
1996 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001997 m = re.match(
1998 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001999 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2000 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002001 line)
2002 if not m:
2003 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002004
Tao Bao818ddf52018-01-05 11:17:34 -08002005 matches = m.groupdict()
2006 cert = matches["CERT"]
2007 privkey = matches["PRIVKEY"]
2008 name = matches["NAME"]
2009 this_compressed_extension = matches["COMPRESSED"]
2010
2011 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2012 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2013 if cert in SPECIAL_CERT_STRINGS and not privkey:
2014 certmap[name] = cert
2015 elif (cert.endswith(OPTIONS.public_key_suffix) and
2016 privkey.endswith(OPTIONS.private_key_suffix) and
2017 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2018 certmap[name] = cert[:-public_key_suffix_len]
2019 else:
2020 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2021
2022 if not this_compressed_extension:
2023 continue
2024
2025 # Only count the installed files.
2026 filename = name + '.' + this_compressed_extension
2027 if filename not in installed_files:
2028 continue
2029
2030 # Make sure that all the values in the compression map have the same
2031 # extension. We don't support multiple compression methods in the same
2032 # system image.
2033 if compressed_extension:
2034 if this_compressed_extension != compressed_extension:
2035 raise ValueError(
2036 "Multiple compressed extensions: {} vs {}".format(
2037 compressed_extension, this_compressed_extension))
2038 else:
2039 compressed_extension = this_compressed_extension
2040
2041 return (certmap,
2042 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002043
2044
Doug Zongkereef39442009-04-02 12:14:19 -07002045COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002046Global options
2047
2048 -p (--path) <dir>
2049 Prepend <dir>/bin to the list of places to search for binaries run by this
2050 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002051
Doug Zongker05d3dea2009-06-22 11:32:31 -07002052 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002053 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002054
Tao Bao30df8b42018-04-23 15:32:53 -07002055 -x (--extra) <key=value>
2056 Add a key/value pair to the 'extras' dict, which device-specific extension
2057 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002058
Doug Zongkereef39442009-04-02 12:14:19 -07002059 -v (--verbose)
2060 Show command lines being executed.
2061
2062 -h (--help)
2063 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002064
2065 --logfile <file>
2066 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002067"""
2068
Kelvin Zhang0876c412020-06-23 15:06:58 -04002069
Doug Zongkereef39442009-04-02 12:14:19 -07002070def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002071 print(docstring.rstrip("\n"))
2072 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002073
2074
2075def ParseOptions(argv,
2076 docstring,
2077 extra_opts="", extra_long_opts=(),
2078 extra_option_handler=None):
2079 """Parse the options in argv and return any arguments that aren't
2080 flags. docstring is the calling module's docstring, to be displayed
2081 for errors and -h. extra_opts and extra_long_opts are for flags
2082 defined by the caller, which are processed by passing them to
2083 extra_option_handler."""
2084
2085 try:
2086 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002087 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002088 ["help", "verbose", "path=", "signapk_path=",
2089 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002090 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002091 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2092 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002093 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2094 "aftl_key_path=", "aftl_manufacturer_key_path=",
2095 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002096 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002097 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002098 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002099 sys.exit(2)
2100
Doug Zongkereef39442009-04-02 12:14:19 -07002101 for o, a in opts:
2102 if o in ("-h", "--help"):
2103 Usage(docstring)
2104 sys.exit()
2105 elif o in ("-v", "--verbose"):
2106 OPTIONS.verbose = True
2107 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002108 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002109 elif o in ("--signapk_path",):
2110 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002111 elif o in ("--signapk_shared_library_path",):
2112 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002113 elif o in ("--extra_signapk_args",):
2114 OPTIONS.extra_signapk_args = shlex.split(a)
2115 elif o in ("--java_path",):
2116 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002117 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002118 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002119 elif o in ("--android_jar_path",):
2120 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002121 elif o in ("--public_key_suffix",):
2122 OPTIONS.public_key_suffix = a
2123 elif o in ("--private_key_suffix",):
2124 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002125 elif o in ("--boot_signer_path",):
2126 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002127 elif o in ("--boot_signer_args",):
2128 OPTIONS.boot_signer_args = shlex.split(a)
2129 elif o in ("--verity_signer_path",):
2130 OPTIONS.verity_signer_path = a
2131 elif o in ("--verity_signer_args",):
2132 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002133 elif o in ("--aftl_tool_path",):
2134 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002135 elif o in ("--aftl_server",):
2136 OPTIONS.aftl_server = a
2137 elif o in ("--aftl_key_path",):
2138 OPTIONS.aftl_key_path = a
2139 elif o in ("--aftl_manufacturer_key_path",):
2140 OPTIONS.aftl_manufacturer_key_path = a
2141 elif o in ("--aftl_signer_helper",):
2142 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002143 elif o in ("-s", "--device_specific"):
2144 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002145 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002146 key, value = a.split("=", 1)
2147 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002148 elif o in ("--logfile",):
2149 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002150 else:
2151 if extra_option_handler is None or not extra_option_handler(o, a):
2152 assert False, "unknown option \"%s\"" % (o,)
2153
Doug Zongker85448772014-09-09 14:59:20 -07002154 if OPTIONS.search_path:
2155 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2156 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002157
2158 return args
2159
2160
Tao Bao4c851b12016-09-19 13:54:38 -07002161def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002162 """Make a temp file and add it to the list of things to be deleted
2163 when Cleanup() is called. Return the filename."""
2164 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2165 os.close(fd)
2166 OPTIONS.tempfiles.append(fn)
2167 return fn
2168
2169
Tao Bao1c830bf2017-12-25 10:43:47 -08002170def MakeTempDir(prefix='tmp', suffix=''):
2171 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2172
2173 Returns:
2174 The absolute pathname of the new directory.
2175 """
2176 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2177 OPTIONS.tempfiles.append(dir_name)
2178 return dir_name
2179
2180
Doug Zongkereef39442009-04-02 12:14:19 -07002181def Cleanup():
2182 for i in OPTIONS.tempfiles:
2183 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002184 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002185 else:
2186 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002187 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002188
2189
2190class PasswordManager(object):
2191 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002192 self.editor = os.getenv("EDITOR")
2193 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002194
2195 def GetPasswords(self, items):
2196 """Get passwords corresponding to each string in 'items',
2197 returning a dict. (The dict may have keys in addition to the
2198 values in 'items'.)
2199
2200 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2201 user edit that file to add more needed passwords. If no editor is
2202 available, or $ANDROID_PW_FILE isn't define, prompts the user
2203 interactively in the ordinary way.
2204 """
2205
2206 current = self.ReadFile()
2207
2208 first = True
2209 while True:
2210 missing = []
2211 for i in items:
2212 if i not in current or not current[i]:
2213 missing.append(i)
2214 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002215 if not missing:
2216 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002217
2218 for i in missing:
2219 current[i] = ""
2220
2221 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002222 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002223 if sys.version_info[0] >= 3:
2224 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002225 answer = raw_input("try to edit again? [y]> ").strip()
2226 if answer and answer[0] not in 'yY':
2227 raise RuntimeError("key passwords unavailable")
2228 first = False
2229
2230 current = self.UpdateAndReadFile(current)
2231
Kelvin Zhang0876c412020-06-23 15:06:58 -04002232 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002233 """Prompt the user to enter a value (password) for each key in
2234 'current' whose value is fales. Returns a new dict with all the
2235 values.
2236 """
2237 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002238 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002239 if v:
2240 result[k] = v
2241 else:
2242 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002243 result[k] = getpass.getpass(
2244 "Enter password for %s key> " % k).strip()
2245 if result[k]:
2246 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002247 return result
2248
2249 def UpdateAndReadFile(self, current):
2250 if not self.editor or not self.pwfile:
2251 return self.PromptResult(current)
2252
2253 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002254 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002255 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2256 f.write("# (Additional spaces are harmless.)\n\n")
2257
2258 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002259 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002260 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002261 f.write("[[[ %s ]]] %s\n" % (v, k))
2262 if not v and first_line is None:
2263 # position cursor on first line with no password.
2264 first_line = i + 4
2265 f.close()
2266
Tao Bao986ee862018-10-04 15:46:16 -07002267 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002268
2269 return self.ReadFile()
2270
2271 def ReadFile(self):
2272 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002273 if self.pwfile is None:
2274 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002275 try:
2276 f = open(self.pwfile, "r")
2277 for line in f:
2278 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002279 if not line or line[0] == '#':
2280 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002281 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2282 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002283 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002284 else:
2285 result[m.group(2)] = m.group(1)
2286 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002287 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002288 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002289 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002290 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002291
2292
Dan Albert8e0178d2015-01-27 15:53:15 -08002293def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2294 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002295
2296 # http://b/18015246
2297 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2298 # for files larger than 2GiB. We can work around this by adjusting their
2299 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2300 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2301 # it isn't clear to me exactly what circumstances cause this).
2302 # `zipfile.write()` must be used directly to work around this.
2303 #
2304 # This mess can be avoided if we port to python3.
2305 saved_zip64_limit = zipfile.ZIP64_LIMIT
2306 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2307
2308 if compress_type is None:
2309 compress_type = zip_file.compression
2310 if arcname is None:
2311 arcname = filename
2312
2313 saved_stat = os.stat(filename)
2314
2315 try:
2316 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2317 # file to be zipped and reset it when we're done.
2318 os.chmod(filename, perms)
2319
2320 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002321 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2322 # intentional. zip stores datetimes in local time without a time zone
2323 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2324 # in the zip archive.
2325 local_epoch = datetime.datetime.fromtimestamp(0)
2326 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002327 os.utime(filename, (timestamp, timestamp))
2328
2329 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2330 finally:
2331 os.chmod(filename, saved_stat.st_mode)
2332 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2333 zipfile.ZIP64_LIMIT = saved_zip64_limit
2334
2335
Tao Bao58c1b962015-05-20 09:32:18 -07002336def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002337 compress_type=None):
2338 """Wrap zipfile.writestr() function to work around the zip64 limit.
2339
2340 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2341 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2342 when calling crc32(bytes).
2343
2344 But it still works fine to write a shorter string into a large zip file.
2345 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2346 when we know the string won't be too long.
2347 """
2348
2349 saved_zip64_limit = zipfile.ZIP64_LIMIT
2350 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2351
2352 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2353 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002354 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002355 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002356 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002357 else:
Tao Baof3282b42015-04-01 11:21:55 -07002358 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002359 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2360 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2361 # such a case (since
2362 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2363 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2364 # permission bits. We follow the logic in Python 3 to get consistent
2365 # behavior between using the two versions.
2366 if not zinfo.external_attr:
2367 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002368
2369 # If compress_type is given, it overrides the value in zinfo.
2370 if compress_type is not None:
2371 zinfo.compress_type = compress_type
2372
Tao Bao58c1b962015-05-20 09:32:18 -07002373 # If perms is given, it has a priority.
2374 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002375 # If perms doesn't set the file type, mark it as a regular file.
2376 if perms & 0o770000 == 0:
2377 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002378 zinfo.external_attr = perms << 16
2379
Tao Baof3282b42015-04-01 11:21:55 -07002380 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002381 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2382
Dan Albert8b72aef2015-03-23 19:13:21 -07002383 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002384 zipfile.ZIP64_LIMIT = saved_zip64_limit
2385
2386
Tao Bao89d7ab22017-12-14 17:05:33 -08002387def ZipDelete(zip_filename, entries):
2388 """Deletes entries from a ZIP file.
2389
2390 Since deleting entries from a ZIP file is not supported, it shells out to
2391 'zip -d'.
2392
2393 Args:
2394 zip_filename: The name of the ZIP file.
2395 entries: The name of the entry, or the list of names to be deleted.
2396
2397 Raises:
2398 AssertionError: In case of non-zero return from 'zip'.
2399 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002400 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002401 entries = [entries]
2402 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002403 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002404
2405
Tao Baof3282b42015-04-01 11:21:55 -07002406def ZipClose(zip_file):
2407 # http://b/18015246
2408 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2409 # central directory.
2410 saved_zip64_limit = zipfile.ZIP64_LIMIT
2411 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2412
2413 zip_file.close()
2414
2415 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002416
2417
2418class DeviceSpecificParams(object):
2419 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002420
Doug Zongker05d3dea2009-06-22 11:32:31 -07002421 def __init__(self, **kwargs):
2422 """Keyword arguments to the constructor become attributes of this
2423 object, which is passed to all functions in the device-specific
2424 module."""
Tao Bao38884282019-07-10 22:20:56 -07002425 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002426 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002427 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002428
2429 if self.module is None:
2430 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002431 if not path:
2432 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002433 try:
2434 if os.path.isdir(path):
2435 info = imp.find_module("releasetools", [path])
2436 else:
2437 d, f = os.path.split(path)
2438 b, x = os.path.splitext(f)
2439 if x == ".py":
2440 f = b
2441 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002442 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002443 self.module = imp.load_module("device_specific", *info)
2444 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002445 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002446
2447 def _DoCall(self, function_name, *args, **kwargs):
2448 """Call the named function in the device-specific module, passing
2449 the given args and kwargs. The first argument to the call will be
2450 the DeviceSpecific object itself. If there is no module, or the
2451 module does not define the function, return the value of the
2452 'default' kwarg (which itself defaults to None)."""
2453 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002454 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002455 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2456
2457 def FullOTA_Assertions(self):
2458 """Called after emitting the block of assertions at the top of a
2459 full OTA package. Implementations can add whatever additional
2460 assertions they like."""
2461 return self._DoCall("FullOTA_Assertions")
2462
Doug Zongkere5ff5902012-01-17 10:55:37 -08002463 def FullOTA_InstallBegin(self):
2464 """Called at the start of full OTA installation."""
2465 return self._DoCall("FullOTA_InstallBegin")
2466
Yifan Hong10c530d2018-12-27 17:34:18 -08002467 def FullOTA_GetBlockDifferences(self):
2468 """Called during full OTA installation and verification.
2469 Implementation should return a list of BlockDifference objects describing
2470 the update on each additional partitions.
2471 """
2472 return self._DoCall("FullOTA_GetBlockDifferences")
2473
Doug Zongker05d3dea2009-06-22 11:32:31 -07002474 def FullOTA_InstallEnd(self):
2475 """Called at the end of full OTA installation; typically this is
2476 used to install the image for the device's baseband processor."""
2477 return self._DoCall("FullOTA_InstallEnd")
2478
2479 def IncrementalOTA_Assertions(self):
2480 """Called after emitting the block of assertions at the top of an
2481 incremental OTA package. Implementations can add whatever
2482 additional assertions they like."""
2483 return self._DoCall("IncrementalOTA_Assertions")
2484
Doug Zongkere5ff5902012-01-17 10:55:37 -08002485 def IncrementalOTA_VerifyBegin(self):
2486 """Called at the start of the verification phase of incremental
2487 OTA installation; additional checks can be placed here to abort
2488 the script before any changes are made."""
2489 return self._DoCall("IncrementalOTA_VerifyBegin")
2490
Doug Zongker05d3dea2009-06-22 11:32:31 -07002491 def IncrementalOTA_VerifyEnd(self):
2492 """Called at the end of the verification phase of incremental OTA
2493 installation; additional checks can be placed here to abort the
2494 script before any changes are made."""
2495 return self._DoCall("IncrementalOTA_VerifyEnd")
2496
Doug Zongkere5ff5902012-01-17 10:55:37 -08002497 def IncrementalOTA_InstallBegin(self):
2498 """Called at the start of incremental OTA installation (after
2499 verification is complete)."""
2500 return self._DoCall("IncrementalOTA_InstallBegin")
2501
Yifan Hong10c530d2018-12-27 17:34:18 -08002502 def IncrementalOTA_GetBlockDifferences(self):
2503 """Called during incremental OTA installation and verification.
2504 Implementation should return a list of BlockDifference objects describing
2505 the update on each additional partitions.
2506 """
2507 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2508
Doug Zongker05d3dea2009-06-22 11:32:31 -07002509 def IncrementalOTA_InstallEnd(self):
2510 """Called at the end of incremental OTA installation; typically
2511 this is used to install the image for the device's baseband
2512 processor."""
2513 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002514
Tao Bao9bc6bb22015-11-09 16:58:28 -08002515 def VerifyOTA_Assertions(self):
2516 return self._DoCall("VerifyOTA_Assertions")
2517
Tao Bao76def242017-11-21 09:25:31 -08002518
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002519class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002520 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002521 self.name = name
2522 self.data = data
2523 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002524 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002525 self.sha1 = sha1(data).hexdigest()
2526
2527 @classmethod
2528 def FromLocalFile(cls, name, diskname):
2529 f = open(diskname, "rb")
2530 data = f.read()
2531 f.close()
2532 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002533
2534 def WriteToTemp(self):
2535 t = tempfile.NamedTemporaryFile()
2536 t.write(self.data)
2537 t.flush()
2538 return t
2539
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002540 def WriteToDir(self, d):
2541 with open(os.path.join(d, self.name), "wb") as fp:
2542 fp.write(self.data)
2543
Geremy Condra36bd3652014-02-06 19:45:10 -08002544 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002545 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002546
Tao Bao76def242017-11-21 09:25:31 -08002547
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002548DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002549 ".gz": "imgdiff",
2550 ".zip": ["imgdiff", "-z"],
2551 ".jar": ["imgdiff", "-z"],
2552 ".apk": ["imgdiff", "-z"],
2553 ".img": "imgdiff",
2554}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002555
Tao Bao76def242017-11-21 09:25:31 -08002556
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002557class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002558 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002559 self.tf = tf
2560 self.sf = sf
2561 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002562 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002563
2564 def ComputePatch(self):
2565 """Compute the patch (as a string of data) needed to turn sf into
2566 tf. Returns the same tuple as GetPatch()."""
2567
2568 tf = self.tf
2569 sf = self.sf
2570
Doug Zongker24cd2802012-08-14 16:36:15 -07002571 if self.diff_program:
2572 diff_program = self.diff_program
2573 else:
2574 ext = os.path.splitext(tf.name)[1]
2575 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002576
2577 ttemp = tf.WriteToTemp()
2578 stemp = sf.WriteToTemp()
2579
2580 ext = os.path.splitext(tf.name)[1]
2581
2582 try:
2583 ptemp = tempfile.NamedTemporaryFile()
2584 if isinstance(diff_program, list):
2585 cmd = copy.copy(diff_program)
2586 else:
2587 cmd = [diff_program]
2588 cmd.append(stemp.name)
2589 cmd.append(ttemp.name)
2590 cmd.append(ptemp.name)
2591 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002592 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002593
Doug Zongkerf8340082014-08-05 10:39:37 -07002594 def run():
2595 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002596 if e:
2597 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002598 th = threading.Thread(target=run)
2599 th.start()
2600 th.join(timeout=300) # 5 mins
2601 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002602 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002603 p.terminate()
2604 th.join(5)
2605 if th.is_alive():
2606 p.kill()
2607 th.join()
2608
Tianjie Xua2a9f992018-01-05 15:15:54 -08002609 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002610 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002611 self.patch = None
2612 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002613 diff = ptemp.read()
2614 finally:
2615 ptemp.close()
2616 stemp.close()
2617 ttemp.close()
2618
2619 self.patch = diff
2620 return self.tf, self.sf, self.patch
2621
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002622 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002623 """Returns a tuple of (target_file, source_file, patch_data).
2624
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002625 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002626 computing the patch failed.
2627 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002628 return self.tf, self.sf, self.patch
2629
2630
2631def ComputeDifferences(diffs):
2632 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002633 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002634
2635 # Do the largest files first, to try and reduce the long-pole effect.
2636 by_size = [(i.tf.size, i) for i in diffs]
2637 by_size.sort(reverse=True)
2638 by_size = [i[1] for i in by_size]
2639
2640 lock = threading.Lock()
2641 diff_iter = iter(by_size) # accessed under lock
2642
2643 def worker():
2644 try:
2645 lock.acquire()
2646 for d in diff_iter:
2647 lock.release()
2648 start = time.time()
2649 d.ComputePatch()
2650 dur = time.time() - start
2651 lock.acquire()
2652
2653 tf, sf, patch = d.GetPatch()
2654 if sf.name == tf.name:
2655 name = tf.name
2656 else:
2657 name = "%s (%s)" % (tf.name, sf.name)
2658 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002659 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002660 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002661 logger.info(
2662 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2663 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002664 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002665 except Exception:
2666 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002667 raise
2668
2669 # start worker threads; wait for them all to finish.
2670 threads = [threading.Thread(target=worker)
2671 for i in range(OPTIONS.worker_threads)]
2672 for th in threads:
2673 th.start()
2674 while threads:
2675 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002676
2677
Dan Albert8b72aef2015-03-23 19:13:21 -07002678class BlockDifference(object):
2679 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002680 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002681 self.tgt = tgt
2682 self.src = src
2683 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002684 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002685 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002686
Tao Baodd2a5892015-03-12 12:32:37 -07002687 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002688 version = max(
2689 int(i) for i in
2690 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002691 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002692 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002693
Tianjie Xu41976c72019-07-03 13:57:01 -07002694 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2695 version=self.version,
2696 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002697 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002698 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002699 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002700 self.touched_src_ranges = b.touched_src_ranges
2701 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002702
Yifan Hong10c530d2018-12-27 17:34:18 -08002703 # On devices with dynamic partitions, for new partitions,
2704 # src is None but OPTIONS.source_info_dict is not.
2705 if OPTIONS.source_info_dict is None:
2706 is_dynamic_build = OPTIONS.info_dict.get(
2707 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002708 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002709 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002710 is_dynamic_build = OPTIONS.source_info_dict.get(
2711 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002712 is_dynamic_source = partition in shlex.split(
2713 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002714
Yifan Hongbb2658d2019-01-25 12:30:58 -08002715 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002716 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2717
Yifan Hongbb2658d2019-01-25 12:30:58 -08002718 # For dynamic partitions builds, check partition list in both source
2719 # and target build because new partitions may be added, and existing
2720 # partitions may be removed.
2721 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2722
Yifan Hong10c530d2018-12-27 17:34:18 -08002723 if is_dynamic:
2724 self.device = 'map_partition("%s")' % partition
2725 else:
2726 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002727 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2728 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002729 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002730 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2731 OPTIONS.source_info_dict)
2732 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002733
Tao Baod8d14be2016-02-04 14:26:02 -08002734 @property
2735 def required_cache(self):
2736 return self._required_cache
2737
Tao Bao76def242017-11-21 09:25:31 -08002738 def WriteScript(self, script, output_zip, progress=None,
2739 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002740 if not self.src:
2741 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002742 script.Print("Patching %s image unconditionally..." % (self.partition,))
2743 else:
2744 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002745
Dan Albert8b72aef2015-03-23 19:13:21 -07002746 if progress:
2747 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002748 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002749
2750 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002751 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002752
Tao Bao9bc6bb22015-11-09 16:58:28 -08002753 def WriteStrictVerifyScript(self, script):
2754 """Verify all the blocks in the care_map, including clobbered blocks.
2755
2756 This differs from the WriteVerifyScript() function: a) it prints different
2757 error messages; b) it doesn't allow half-way updated images to pass the
2758 verification."""
2759
2760 partition = self.partition
2761 script.Print("Verifying %s..." % (partition,))
2762 ranges = self.tgt.care_map
2763 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002764 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002765 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2766 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002767 self.device, ranges_str,
2768 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002769 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002770 script.AppendExtra("")
2771
Tao Baod522bdc2016-04-12 15:53:16 -07002772 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002773 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002774
2775 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002776 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002777 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002778
2779 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002780 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002781 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002782 ranges = self.touched_src_ranges
2783 expected_sha1 = self.touched_src_sha1
2784 else:
2785 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2786 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002787
2788 # No blocks to be checked, skipping.
2789 if not ranges:
2790 return
2791
Tao Bao5ece99d2015-05-12 11:42:31 -07002792 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002793 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002794 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002795 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2796 '"%s.patch.dat")) then' % (
2797 self.device, ranges_str, expected_sha1,
2798 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002799 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002800 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002801
Tianjie Xufc3422a2015-12-15 11:53:59 -08002802 if self.version >= 4:
2803
2804 # Bug: 21124327
2805 # When generating incrementals for the system and vendor partitions in
2806 # version 4 or newer, explicitly check the first block (which contains
2807 # the superblock) of the partition to see if it's what we expect. If
2808 # this check fails, give an explicit log message about the partition
2809 # having been remounted R/W (the most likely explanation).
2810 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002811 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002812
2813 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002814 if partition == "system":
2815 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2816 else:
2817 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002818 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002819 'ifelse (block_image_recover({device}, "{ranges}") && '
2820 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002821 'package_extract_file("{partition}.transfer.list"), '
2822 '"{partition}.new.dat", "{partition}.patch.dat"), '
2823 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002824 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002825 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002826 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002827
Tao Baodd2a5892015-03-12 12:32:37 -07002828 # Abort the OTA update. Note that the incremental OTA cannot be applied
2829 # even if it may match the checksum of the target partition.
2830 # a) If version < 3, operations like move and erase will make changes
2831 # unconditionally and damage the partition.
2832 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002833 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002834 if partition == "system":
2835 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2836 else:
2837 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2838 script.AppendExtra((
2839 'abort("E%d: %s partition has unexpected contents");\n'
2840 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002841
Yifan Hong10c530d2018-12-27 17:34:18 -08002842 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002843 partition = self.partition
2844 script.Print('Verifying the updated %s image...' % (partition,))
2845 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2846 ranges = self.tgt.care_map
2847 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002848 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002849 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002850 self.device, ranges_str,
2851 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002852
2853 # Bug: 20881595
2854 # Verify that extended blocks are really zeroed out.
2855 if self.tgt.extended:
2856 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002857 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002858 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002859 self.device, ranges_str,
2860 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002861 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002862 if partition == "system":
2863 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2864 else:
2865 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002866 script.AppendExtra(
2867 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002868 ' abort("E%d: %s partition has unexpected non-zero contents after '
2869 'OTA update");\n'
2870 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002871 else:
2872 script.Print('Verified the updated %s image.' % (partition,))
2873
Tianjie Xu209db462016-05-24 17:34:52 -07002874 if partition == "system":
2875 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2876 else:
2877 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2878
Tao Bao5fcaaef2015-06-01 13:40:49 -07002879 script.AppendExtra(
2880 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002881 ' abort("E%d: %s partition has unexpected contents after OTA '
2882 'update");\n'
2883 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002884
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002885 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002886 ZipWrite(output_zip,
2887 '{}.transfer.list'.format(self.path),
2888 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002889
Tao Bao76def242017-11-21 09:25:31 -08002890 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2891 # its size. Quailty 9 almost triples the compression time but doesn't
2892 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002893 # zip | brotli(quality 6) | brotli(quality 9)
2894 # compressed_size: 942M | 869M (~8% reduced) | 854M
2895 # compression_time: 75s | 265s | 719s
2896 # decompression_time: 15s | 25s | 25s
2897
2898 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002899 brotli_cmd = ['brotli', '--quality=6',
2900 '--output={}.new.dat.br'.format(self.path),
2901 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002902 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002903 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002904
2905 new_data_name = '{}.new.dat.br'.format(self.partition)
2906 ZipWrite(output_zip,
2907 '{}.new.dat.br'.format(self.path),
2908 new_data_name,
2909 compress_type=zipfile.ZIP_STORED)
2910 else:
2911 new_data_name = '{}.new.dat'.format(self.partition)
2912 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2913
Dan Albert8e0178d2015-01-27 15:53:15 -08002914 ZipWrite(output_zip,
2915 '{}.patch.dat'.format(self.path),
2916 '{}.patch.dat'.format(self.partition),
2917 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002918
Tianjie Xu209db462016-05-24 17:34:52 -07002919 if self.partition == "system":
2920 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2921 else:
2922 code = ErrorCode.VENDOR_UPDATE_FAILURE
2923
Yifan Hong10c530d2018-12-27 17:34:18 -08002924 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002925 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002926 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002927 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002928 device=self.device, partition=self.partition,
2929 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002930 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002931
Kelvin Zhang0876c412020-06-23 15:06:58 -04002932 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002933 data = source.ReadRangeSet(ranges)
2934 ctx = sha1()
2935
2936 for p in data:
2937 ctx.update(p)
2938
2939 return ctx.hexdigest()
2940
Kelvin Zhang0876c412020-06-23 15:06:58 -04002941 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07002942 """Return the hash value for all zero blocks."""
2943 zero_block = '\x00' * 4096
2944 ctx = sha1()
2945 for _ in range(num_blocks):
2946 ctx.update(zero_block)
2947
2948 return ctx.hexdigest()
2949
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002950
Tianjie Xu41976c72019-07-03 13:57:01 -07002951# Expose these two classes to support vendor-specific scripts
2952DataImage = images.DataImage
2953EmptyImage = images.EmptyImage
2954
Tao Bao76def242017-11-21 09:25:31 -08002955
Doug Zongker96a57e72010-09-26 14:57:41 -07002956# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002957PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002958 "ext4": "EMMC",
2959 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002960 "f2fs": "EMMC",
2961 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002962}
Doug Zongker96a57e72010-09-26 14:57:41 -07002963
Kelvin Zhang0876c412020-06-23 15:06:58 -04002964
Yifan Hongbdb32012020-05-07 12:38:53 -07002965def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2966 """
2967 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2968 backwards compatibility. It aborts if the fstab entry has slotselect option
2969 (unless check_no_slot is explicitly set to False).
2970 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002971 fstab = info["fstab"]
2972 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07002973 if check_no_slot:
2974 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002975 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002976 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2977 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002978 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002979
2980
Yifan Hongbdb32012020-05-07 12:38:53 -07002981def GetTypeAndDeviceExpr(mount_point, info):
2982 """
2983 Return the filesystem of the partition, and an edify expression that evaluates
2984 to the device at runtime.
2985 """
2986 fstab = info["fstab"]
2987 if fstab:
2988 p = fstab[mount_point]
2989 device_expr = '"%s"' % fstab[mount_point].device
2990 if p.slotselect:
2991 device_expr = 'add_slot_suffix(%s)' % device_expr
2992 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002993 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07002994
2995
2996def GetEntryForDevice(fstab, device):
2997 """
2998 Returns:
2999 The first entry in fstab whose device is the given value.
3000 """
3001 if not fstab:
3002 return None
3003 for mount_point in fstab:
3004 if fstab[mount_point].device == device:
3005 return fstab[mount_point]
3006 return None
3007
Kelvin Zhang0876c412020-06-23 15:06:58 -04003008
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003009def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003010 """Parses and converts a PEM-encoded certificate into DER-encoded.
3011
3012 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3013
3014 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003015 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003016 """
3017 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003018 save = False
3019 for line in data.split("\n"):
3020 if "--END CERTIFICATE--" in line:
3021 break
3022 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003023 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003024 if "--BEGIN CERTIFICATE--" in line:
3025 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003026 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003027 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003028
Tao Bao04e1f012018-02-04 12:13:35 -08003029
3030def ExtractPublicKey(cert):
3031 """Extracts the public key (PEM-encoded) from the given certificate file.
3032
3033 Args:
3034 cert: The certificate filename.
3035
3036 Returns:
3037 The public key string.
3038
3039 Raises:
3040 AssertionError: On non-zero return from 'openssl'.
3041 """
3042 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3043 # While openssl 1.1 writes the key into the given filename followed by '-out',
3044 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3045 # stdout instead.
3046 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3047 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3048 pubkey, stderrdata = proc.communicate()
3049 assert proc.returncode == 0, \
3050 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3051 return pubkey
3052
3053
Tao Bao1ac886e2019-06-26 11:58:22 -07003054def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003055 """Extracts the AVB public key from the given public or private key.
3056
3057 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003058 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003059 key: The input key file, which should be PEM-encoded public or private key.
3060
3061 Returns:
3062 The path to the extracted AVB public key file.
3063 """
3064 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3065 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003066 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003067 return output
3068
3069
Doug Zongker412c02f2014-02-13 10:58:24 -08003070def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3071 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003072 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003073
Tao Bao6d5d6232018-03-09 17:04:42 -08003074 Most of the space in the boot and recovery images is just the kernel, which is
3075 identical for the two, so the resulting patch should be efficient. Add it to
3076 the output zip, along with a shell script that is run from init.rc on first
3077 boot to actually do the patching and install the new recovery image.
3078
3079 Args:
3080 input_dir: The top-level input directory of the target-files.zip.
3081 output_sink: The callback function that writes the result.
3082 recovery_img: File object for the recovery image.
3083 boot_img: File objects for the boot image.
3084 info_dict: A dict returned by common.LoadInfoDict() on the input
3085 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003086 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003087 if info_dict is None:
3088 info_dict = OPTIONS.info_dict
3089
Tao Bao6d5d6232018-03-09 17:04:42 -08003090 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003091 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3092
3093 if board_uses_vendorimage:
3094 # In this case, the output sink is rooted at VENDOR
3095 recovery_img_path = "etc/recovery.img"
3096 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3097 sh_dir = "bin"
3098 else:
3099 # In this case the output sink is rooted at SYSTEM
3100 recovery_img_path = "vendor/etc/recovery.img"
3101 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3102 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003103
Tao Baof2cffbd2015-07-22 12:33:18 -07003104 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003105 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003106
3107 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003108 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003109 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003110 # With system-root-image, boot and recovery images will have mismatching
3111 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3112 # to handle such a case.
3113 if system_root_image:
3114 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003115 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003116 assert not os.path.exists(path)
3117 else:
3118 diff_program = ["imgdiff"]
3119 if os.path.exists(path):
3120 diff_program.append("-b")
3121 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003122 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003123 else:
3124 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003125
3126 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3127 _, _, patch = d.ComputePatch()
3128 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003129
Dan Albertebb19aa2015-03-27 19:11:53 -07003130 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003131 # The following GetTypeAndDevice()s need to use the path in the target
3132 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003133 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3134 check_no_slot=False)
3135 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3136 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003137 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003138 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003139
Tao Baof2cffbd2015-07-22 12:33:18 -07003140 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003141
3142 # Note that we use /vendor to refer to the recovery resources. This will
3143 # work for a separate vendor partition mounted at /vendor or a
3144 # /system/vendor subdirectory on the system partition, for which init will
3145 # create a symlink from /vendor to /system/vendor.
3146
3147 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003148if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3149 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003150 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003151 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3152 log -t recovery "Installing new recovery image: succeeded" || \\
3153 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003154else
3155 log -t recovery "Recovery image already installed"
3156fi
3157""" % {'type': recovery_type,
3158 'device': recovery_device,
3159 'sha1': recovery_img.sha1,
3160 'size': recovery_img.size}
3161 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003162 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003163if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3164 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003165 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003166 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3167 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3168 log -t recovery "Installing new recovery image: succeeded" || \\
3169 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003170else
3171 log -t recovery "Recovery image already installed"
3172fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003173""" % {'boot_size': boot_img.size,
3174 'boot_sha1': boot_img.sha1,
3175 'recovery_size': recovery_img.size,
3176 'recovery_sha1': recovery_img.sha1,
3177 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003178 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
3179 'recovery_type': recovery_type + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003180 'recovery_device': recovery_device,
3181 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003182
Bill Peckhame868aec2019-09-17 17:06:47 -07003183 # The install script location moved from /system/etc to /system/bin in the L
3184 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3185 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003186
Tao Bao32fcdab2018-10-12 10:30:39 -07003187 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003188
Tao Baoda30cfa2017-12-01 16:19:46 -08003189 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003190
3191
3192class DynamicPartitionUpdate(object):
3193 def __init__(self, src_group=None, tgt_group=None, progress=None,
3194 block_difference=None):
3195 self.src_group = src_group
3196 self.tgt_group = tgt_group
3197 self.progress = progress
3198 self.block_difference = block_difference
3199
3200 @property
3201 def src_size(self):
3202 if not self.block_difference:
3203 return 0
3204 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3205
3206 @property
3207 def tgt_size(self):
3208 if not self.block_difference:
3209 return 0
3210 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3211
3212 @staticmethod
3213 def _GetSparseImageSize(img):
3214 if not img:
3215 return 0
3216 return img.blocksize * img.total_blocks
3217
3218
3219class DynamicGroupUpdate(object):
3220 def __init__(self, src_size=None, tgt_size=None):
3221 # None: group does not exist. 0: no size limits.
3222 self.src_size = src_size
3223 self.tgt_size = tgt_size
3224
3225
3226class DynamicPartitionsDifference(object):
3227 def __init__(self, info_dict, block_diffs, progress_dict=None,
3228 source_info_dict=None):
3229 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003230 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003231
3232 self._remove_all_before_apply = False
3233 if source_info_dict is None:
3234 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003235 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003236
Tao Baof1113e92019-06-18 12:10:14 -07003237 block_diff_dict = collections.OrderedDict(
3238 [(e.partition, e) for e in block_diffs])
3239
Yifan Hong10c530d2018-12-27 17:34:18 -08003240 assert len(block_diff_dict) == len(block_diffs), \
3241 "Duplicated BlockDifference object for {}".format(
3242 [partition for partition, count in
3243 collections.Counter(e.partition for e in block_diffs).items()
3244 if count > 1])
3245
Yifan Hong79997e52019-01-23 16:56:19 -08003246 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003247
3248 for p, block_diff in block_diff_dict.items():
3249 self._partition_updates[p] = DynamicPartitionUpdate()
3250 self._partition_updates[p].block_difference = block_diff
3251
3252 for p, progress in progress_dict.items():
3253 if p in self._partition_updates:
3254 self._partition_updates[p].progress = progress
3255
3256 tgt_groups = shlex.split(info_dict.get(
3257 "super_partition_groups", "").strip())
3258 src_groups = shlex.split(source_info_dict.get(
3259 "super_partition_groups", "").strip())
3260
3261 for g in tgt_groups:
3262 for p in shlex.split(info_dict.get(
3263 "super_%s_partition_list" % g, "").strip()):
3264 assert p in self._partition_updates, \
3265 "{} is in target super_{}_partition_list but no BlockDifference " \
3266 "object is provided.".format(p, g)
3267 self._partition_updates[p].tgt_group = g
3268
3269 for g in src_groups:
3270 for p in shlex.split(source_info_dict.get(
3271 "super_%s_partition_list" % g, "").strip()):
3272 assert p in self._partition_updates, \
3273 "{} is in source super_{}_partition_list but no BlockDifference " \
3274 "object is provided.".format(p, g)
3275 self._partition_updates[p].src_group = g
3276
Yifan Hong45433e42019-01-18 13:55:25 -08003277 target_dynamic_partitions = set(shlex.split(info_dict.get(
3278 "dynamic_partition_list", "").strip()))
3279 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3280 if u.tgt_size)
3281 assert block_diffs_with_target == target_dynamic_partitions, \
3282 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3283 list(target_dynamic_partitions), list(block_diffs_with_target))
3284
3285 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3286 "dynamic_partition_list", "").strip()))
3287 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3288 if u.src_size)
3289 assert block_diffs_with_source == source_dynamic_partitions, \
3290 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3291 list(source_dynamic_partitions), list(block_diffs_with_source))
3292
Yifan Hong10c530d2018-12-27 17:34:18 -08003293 if self._partition_updates:
3294 logger.info("Updating dynamic partitions %s",
3295 self._partition_updates.keys())
3296
Yifan Hong79997e52019-01-23 16:56:19 -08003297 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003298
3299 for g in tgt_groups:
3300 self._group_updates[g] = DynamicGroupUpdate()
3301 self._group_updates[g].tgt_size = int(info_dict.get(
3302 "super_%s_group_size" % g, "0").strip())
3303
3304 for g in src_groups:
3305 if g not in self._group_updates:
3306 self._group_updates[g] = DynamicGroupUpdate()
3307 self._group_updates[g].src_size = int(source_info_dict.get(
3308 "super_%s_group_size" % g, "0").strip())
3309
3310 self._Compute()
3311
3312 def WriteScript(self, script, output_zip, write_verify_script=False):
3313 script.Comment('--- Start patching dynamic partitions ---')
3314 for p, u in self._partition_updates.items():
3315 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3316 script.Comment('Patch partition %s' % p)
3317 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3318 write_verify_script=False)
3319
3320 op_list_path = MakeTempFile()
3321 with open(op_list_path, 'w') as f:
3322 for line in self._op_list:
3323 f.write('{}\n'.format(line))
3324
3325 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3326
3327 script.Comment('Update dynamic partition metadata')
3328 script.AppendExtra('assert(update_dynamic_partitions('
3329 'package_extract_file("dynamic_partitions_op_list")));')
3330
3331 if write_verify_script:
3332 for p, u in self._partition_updates.items():
3333 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3334 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003335 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003336
3337 for p, u in self._partition_updates.items():
3338 if u.tgt_size and u.src_size <= u.tgt_size:
3339 script.Comment('Patch partition %s' % p)
3340 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3341 write_verify_script=write_verify_script)
3342 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003343 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003344
3345 script.Comment('--- End patching dynamic partitions ---')
3346
3347 def _Compute(self):
3348 self._op_list = list()
3349
3350 def append(line):
3351 self._op_list.append(line)
3352
3353 def comment(line):
3354 self._op_list.append("# %s" % line)
3355
3356 if self._remove_all_before_apply:
3357 comment('Remove all existing dynamic partitions and groups before '
3358 'applying full OTA')
3359 append('remove_all_groups')
3360
3361 for p, u in self._partition_updates.items():
3362 if u.src_group and not u.tgt_group:
3363 append('remove %s' % p)
3364
3365 for p, u in self._partition_updates.items():
3366 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3367 comment('Move partition %s from %s to default' % (p, u.src_group))
3368 append('move %s default' % p)
3369
3370 for p, u in self._partition_updates.items():
3371 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3372 comment('Shrink partition %s from %d to %d' %
3373 (p, u.src_size, u.tgt_size))
3374 append('resize %s %s' % (p, u.tgt_size))
3375
3376 for g, u in self._group_updates.items():
3377 if u.src_size is not None and u.tgt_size is None:
3378 append('remove_group %s' % g)
3379 if (u.src_size is not None and u.tgt_size is not None and
3380 u.src_size > u.tgt_size):
3381 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3382 append('resize_group %s %d' % (g, u.tgt_size))
3383
3384 for g, u in self._group_updates.items():
3385 if u.src_size is None and u.tgt_size is not None:
3386 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3387 append('add_group %s %d' % (g, u.tgt_size))
3388 if (u.src_size is not None and u.tgt_size is not None and
3389 u.src_size < u.tgt_size):
3390 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3391 append('resize_group %s %d' % (g, u.tgt_size))
3392
3393 for p, u in self._partition_updates.items():
3394 if u.tgt_group and not u.src_group:
3395 comment('Add partition %s to group %s' % (p, u.tgt_group))
3396 append('add %s %s' % (p, u.tgt_group))
3397
3398 for p, u in self._partition_updates.items():
3399 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003400 comment('Grow partition %s from %d to %d' %
3401 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003402 append('resize %s %d' % (p, u.tgt_size))
3403
3404 for p, u in self._partition_updates.items():
3405 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3406 comment('Move partition %s from default to %s' %
3407 (p, u.tgt_group))
3408 append('move %s %s' % (p, u.tgt_group))