blob: f256900790a2e87264e163b2f18521d3dc7e3152 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
112# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700114 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm',
115 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800116
Tao Bao08c190f2019-06-03 23:07:58 -0700117# Chained VBMeta partitions.
118AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
119
Tianjie Xu861f4132018-09-12 11:49:33 -0700120# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400121PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700122 'system',
123 'vendor',
124 'product',
125 'system_ext',
126 'odm',
127 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700128 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400129]
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
131
Tianjie Xu209db462016-05-24 17:34:52 -0700132class ErrorCode(object):
133 """Define error_codes for failures that happen during the actual
134 update package installation.
135
136 Error codes 0-999 are reserved for failures before the package
137 installation (i.e. low battery, package verification failure).
138 Detailed code in 'bootable/recovery/error_code.h' """
139
140 SYSTEM_VERIFICATION_FAILURE = 1000
141 SYSTEM_UPDATE_FAILURE = 1001
142 SYSTEM_UNEXPECTED_CONTENTS = 1002
143 SYSTEM_NONZERO_CONTENTS = 1003
144 SYSTEM_RECOVER_FAILURE = 1004
145 VENDOR_VERIFICATION_FAILURE = 2000
146 VENDOR_UPDATE_FAILURE = 2001
147 VENDOR_UNEXPECTED_CONTENTS = 2002
148 VENDOR_NONZERO_CONTENTS = 2003
149 VENDOR_RECOVER_FAILURE = 2004
150 OEM_PROP_MISMATCH = 3000
151 FINGERPRINT_MISMATCH = 3001
152 THUMBPRINT_MISMATCH = 3002
153 OLDER_BUILD = 3003
154 DEVICE_MISMATCH = 3004
155 BAD_PATCH_FILE = 3005
156 INSUFFICIENT_CACHE_SPACE = 3006
157 TUNE_PARTITION_FAILURE = 3007
158 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800159
Tao Bao80921982018-03-21 21:02:19 -0700160
Dan Albert8b72aef2015-03-23 19:13:21 -0700161class ExternalError(RuntimeError):
162 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700163
164
Tao Bao32fcdab2018-10-12 10:30:39 -0700165def InitLogging():
166 DEFAULT_LOGGING_CONFIG = {
167 'version': 1,
168 'disable_existing_loggers': False,
169 'formatters': {
170 'standard': {
171 'format':
172 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
173 'datefmt': '%Y-%m-%d %H:%M:%S',
174 },
175 },
176 'handlers': {
177 'default': {
178 'class': 'logging.StreamHandler',
179 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700180 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700181 },
182 },
183 'loggers': {
184 '': {
185 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700186 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700187 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700188 }
189 }
190 }
191 env_config = os.getenv('LOGGING_CONFIG')
192 if env_config:
193 with open(env_config) as f:
194 config = json.load(f)
195 else:
196 config = DEFAULT_LOGGING_CONFIG
197
198 # Increase the logging level for verbose mode.
199 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700200 config = copy.deepcopy(config)
201 config['handlers']['default']['level'] = 'INFO'
202
203 if OPTIONS.logfile:
204 config = copy.deepcopy(config)
205 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400206 'class': 'logging.FileHandler',
207 'formatter': 'standard',
208 'level': 'INFO',
209 'mode': 'w',
210 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700211 }
212 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700213
214 logging.config.dictConfig(config)
215
216
Yifan Hong8e332ff2020-07-29 17:51:55 -0700217def SetHostToolLocation(tool_name, location):
218 OPTIONS.host_tools[tool_name] = location
219
220
Tao Bao39451582017-05-04 11:10:47 -0700221def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700222 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700223
Tao Bao73dd4f42018-10-04 16:25:33 -0700224 Args:
225 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700226 verbose: Whether the commands should be shown. Default to the global
227 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700228 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
229 stdin, etc. stdout and stderr will default to subprocess.PIPE and
230 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800231 universal_newlines will default to True, as most of the users in
232 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700233
234 Returns:
235 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700236 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700237 if 'stdout' not in kwargs and 'stderr' not in kwargs:
238 kwargs['stdout'] = subprocess.PIPE
239 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800240 if 'universal_newlines' not in kwargs:
241 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700242
243 # If explicitly set host tool location before, use that location to avoid
244 # PATH violation. Make a copy of args in case client relies on the content
245 # of args later.
246 if args and args[0] in OPTIONS.host_tools:
247 args = args[:]
248 args[0] = OPTIONS.host_tools[args[0]]
249
Tao Bao32fcdab2018-10-12 10:30:39 -0700250 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400251 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700252 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700253 return subprocess.Popen(args, **kwargs)
254
255
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800256def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800257 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800258
259 Args:
260 args: The command represented as a list of strings.
261 verbose: Whether the commands should be shown. Default to the global
262 verbosity if unspecified.
263 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
264 stdin, etc. stdout and stderr will default to subprocess.PIPE and
265 subprocess.STDOUT respectively unless caller specifies any of them.
266
Bill Peckham889b0c62019-02-21 18:53:37 -0800267 Raises:
268 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800269 """
270 proc = Run(args, verbose=verbose, **kwargs)
271 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800272
273 if proc.returncode != 0:
274 raise ExternalError(
275 "Failed to run command '{}' (exit code {})".format(
276 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800277
278
Tao Bao986ee862018-10-04 15:46:16 -0700279def RunAndCheckOutput(args, verbose=None, **kwargs):
280 """Runs the given command and returns the output.
281
282 Args:
283 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700284 verbose: Whether the commands should be shown. Default to the global
285 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700286 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
287 stdin, etc. stdout and stderr will default to subprocess.PIPE and
288 subprocess.STDOUT respectively unless caller specifies any of them.
289
290 Returns:
291 The output string.
292
293 Raises:
294 ExternalError: On non-zero exit from the command.
295 """
Tao Bao986ee862018-10-04 15:46:16 -0700296 proc = Run(args, verbose=verbose, **kwargs)
297 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800298 if output is None:
299 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700300 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400301 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700302 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700303 if proc.returncode != 0:
304 raise ExternalError(
305 "Failed to run command '{}' (exit code {}):\n{}".format(
306 args, proc.returncode, output))
307 return output
308
309
Tao Baoc765cca2018-01-31 17:32:40 -0800310def RoundUpTo4K(value):
311 rounded_up = value + 4095
312 return rounded_up - (rounded_up % 4096)
313
314
Ying Wang7e6d4e42010-12-13 16:25:36 -0800315def CloseInheritedPipes():
316 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
317 before doing other work."""
318 if platform.system() != "Darwin":
319 return
320 for d in range(3, 1025):
321 try:
322 stat = os.fstat(d)
323 if stat is not None:
324 pipebit = stat[0] & 0x1000
325 if pipebit != 0:
326 os.close(d)
327 except OSError:
328 pass
329
330
Tao Bao1c320f82019-10-04 23:25:12 -0700331class BuildInfo(object):
332 """A class that holds the information for a given build.
333
334 This class wraps up the property querying for a given source or target build.
335 It abstracts away the logic of handling OEM-specific properties, and caches
336 the commonly used properties such as fingerprint.
337
338 There are two types of info dicts: a) build-time info dict, which is generated
339 at build time (i.e. included in a target_files zip); b) OEM info dict that is
340 specified at package generation time (via command line argument
341 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
342 having "oem_fingerprint_properties" in build-time info dict), all the queries
343 would be answered based on build-time info dict only. Otherwise if using
344 OEM-specific properties, some of them will be calculated from two info dicts.
345
346 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800347 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700348
349 Attributes:
350 info_dict: The build-time info dict.
351 is_ab: Whether it's a build that uses A/B OTA.
352 oem_dicts: A list of OEM dicts.
353 oem_props: A list of OEM properties that should be read from OEM dicts; None
354 if the build doesn't use any OEM-specific property.
355 fingerprint: The fingerprint of the build, which would be calculated based
356 on OEM properties if applicable.
357 device: The device name, which could come from OEM dicts if applicable.
358 """
359
360 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
361 "ro.product.manufacturer", "ro.product.model",
362 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700363 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
364 "product", "odm", "vendor", "system_ext", "system"]
365 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
366 "product", "product_services", "odm", "vendor", "system"]
367 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700368
Tao Bao3ed35d32019-10-07 20:48:48 -0700369 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700370 """Initializes a BuildInfo instance with the given dicts.
371
372 Note that it only wraps up the given dicts, without making copies.
373
374 Arguments:
375 info_dict: The build-time info dict.
376 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
377 that it always uses the first dict to calculate the fingerprint or the
378 device name. The rest would be used for asserting OEM properties only
379 (e.g. one package can be installed on one of these devices).
380
381 Raises:
382 ValueError: On invalid inputs.
383 """
384 self.info_dict = info_dict
385 self.oem_dicts = oem_dicts
386
387 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700388
Hongguang Chend7c160f2020-05-03 21:24:26 -0700389 # Skip _oem_props if oem_dicts is None to use BuildInfo in
390 # sign_target_files_apks
391 if self.oem_dicts:
392 self._oem_props = info_dict.get("oem_fingerprint_properties")
393 else:
394 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700395
Daniel Normand5fe8622020-01-08 17:01:11 -0800396 def check_fingerprint(fingerprint):
397 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
398 raise ValueError(
399 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
400 "3.2.2. Build Parameters.".format(fingerprint))
401
Daniel Normand5fe8622020-01-08 17:01:11 -0800402 self._partition_fingerprints = {}
403 for partition in PARTITIONS_WITH_CARE_MAP:
404 try:
405 fingerprint = self.CalculatePartitionFingerprint(partition)
406 check_fingerprint(fingerprint)
407 self._partition_fingerprints[partition] = fingerprint
408 except ExternalError:
409 continue
410 if "system" in self._partition_fingerprints:
411 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
412 # need a fingerprint when creating the image.
413 self._partition_fingerprints[
414 "system_other"] = self._partition_fingerprints["system"]
415
Tao Bao1c320f82019-10-04 23:25:12 -0700416 # These two should be computed only after setting self._oem_props.
417 self._device = self.GetOemProperty("ro.product.device")
418 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800419 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700420
421 @property
422 def is_ab(self):
423 return self._is_ab
424
425 @property
426 def device(self):
427 return self._device
428
429 @property
430 def fingerprint(self):
431 return self._fingerprint
432
433 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700434 def oem_props(self):
435 return self._oem_props
436
437 def __getitem__(self, key):
438 return self.info_dict[key]
439
440 def __setitem__(self, key, value):
441 self.info_dict[key] = value
442
443 def get(self, key, default=None):
444 return self.info_dict.get(key, default)
445
446 def items(self):
447 return self.info_dict.items()
448
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000449 def _GetRawBuildProp(self, prop, partition):
450 prop_file = '{}.build.prop'.format(
451 partition) if partition else 'build.prop'
452 partition_props = self.info_dict.get(prop_file)
453 if not partition_props:
454 return None
455 return partition_props.GetProp(prop)
456
Daniel Normand5fe8622020-01-08 17:01:11 -0800457 def GetPartitionBuildProp(self, prop, partition):
458 """Returns the inquired build property for the provided partition."""
459 # If provided a partition for this property, only look within that
460 # partition's build.prop.
461 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
462 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
463 else:
464 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000465
466 prop_val = self._GetRawBuildProp(prop, partition)
467 if prop_val is not None:
468 return prop_val
469 raise ExternalError("couldn't find %s in %s.build.prop" %
470 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800471
Tao Bao1c320f82019-10-04 23:25:12 -0700472 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800473 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700474 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
475 return self._ResolveRoProductBuildProp(prop)
476
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000477 prop_val = self._GetRawBuildProp(prop, None)
478 if prop_val is not None:
479 return prop_val
480
481 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700482
483 def _ResolveRoProductBuildProp(self, prop):
484 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000485 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700486 if prop_val:
487 return prop_val
488
Steven Laver8e2086e2020-04-27 16:26:31 -0700489 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000490 source_order_val = self._GetRawBuildProp(
491 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700492 if source_order_val:
493 source_order = source_order_val.split(",")
494 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700495 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700496
497 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700498 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700499 raise ExternalError(
500 "Invalid ro.product.property_source_order '{}'".format(source_order))
501
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000502 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700503 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000504 "ro.product", "ro.product.{}".format(source_partition), 1)
505 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700506 if prop_val:
507 return prop_val
508
509 raise ExternalError("couldn't resolve {}".format(prop))
510
Steven Laver8e2086e2020-04-27 16:26:31 -0700511 def _GetRoProductPropsDefaultSourceOrder(self):
512 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
513 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000514 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700515 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000516 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700517 if android_version == "10":
518 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
519 # NOTE: float() conversion of android_version will have rounding error.
520 # We are checking for "9" or less, and using "< 10" is well outside of
521 # possible floating point rounding.
522 try:
523 android_version_val = float(android_version)
524 except ValueError:
525 android_version_val = 0
526 if android_version_val < 10:
527 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
528 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
529
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()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400732 try:
733 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
734 except KeyError:
735 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700736 return d
737
Tao Baod1de6f32017-03-01 16:38:48 -0800738
Kelvin Zhang39aea442020-08-17 11:04:25 -0400739
Daniel Norman4cc9df62019-07-18 10:11:07 -0700740def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900741 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700742 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900743
Daniel Norman4cc9df62019-07-18 10:11:07 -0700744
745def LoadDictionaryFromFile(file_path):
746 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900747 return LoadDictionaryFromLines(lines)
748
749
Michael Runge6e836112014-04-15 17:40:21 -0700750def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700751 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700752 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700753 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700754 if not line or line.startswith("#"):
755 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700756 if "=" in line:
757 name, value = line.split("=", 1)
758 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700759 return d
760
Tao Baod1de6f32017-03-01 16:38:48 -0800761
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000762class PartitionBuildProps(object):
763 """The class holds the build prop of a particular partition.
764
765 This class loads the build.prop and holds the build properties for a given
766 partition. It also partially recognizes the 'import' statement in the
767 build.prop; and calculates alternative values of some specific build
768 properties during runtime.
769
770 Attributes:
771 input_file: a zipped target-file or an unzipped target-file directory.
772 partition: name of the partition.
773 props_allow_override: a list of build properties to search for the
774 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000775 build_props: a dict of build properties for the given partition.
776 prop_overrides: a set of props that are overridden by import.
777 placeholder_values: A dict of runtime variables' values to replace the
778 placeholders in the build.prop file. We expect exactly one value for
779 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000780 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400781
Tianjie Xu9afb2212020-05-10 21:48:15 +0000782 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000783 self.input_file = input_file
784 self.partition = name
785 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000786 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000787 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000788 self.prop_overrides = set()
789 self.placeholder_values = {}
790 if placeholder_values:
791 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000792
793 @staticmethod
794 def FromDictionary(name, build_props):
795 """Constructs an instance from a build prop dictionary."""
796
797 props = PartitionBuildProps("unknown", name)
798 props.build_props = build_props.copy()
799 return props
800
801 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000802 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000803 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000804 data = ''
805 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
806 '{}/build.prop'.format(name.upper())]:
807 try:
808 data = ReadFromInputFile(input_file, prop_file)
809 break
810 except KeyError:
811 logger.warning('Failed to read %s', prop_file)
812
Tianjie Xu9afb2212020-05-10 21:48:15 +0000813 props = PartitionBuildProps(input_file, name, placeholder_values)
814 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000815 return props
816
Yifan Hong125d0b62020-09-24 17:07:03 -0700817 @staticmethod
818 def FromBuildPropFile(name, build_prop_file):
819 """Constructs an instance from a build prop file."""
820
821 props = PartitionBuildProps("unknown", name)
822 with open(build_prop_file) as f:
823 props._LoadBuildProp(f.read())
824 return props
825
Tianjie Xu9afb2212020-05-10 21:48:15 +0000826 def _LoadBuildProp(self, data):
827 for line in data.split('\n'):
828 line = line.strip()
829 if not line or line.startswith("#"):
830 continue
831 if line.startswith("import"):
832 overrides = self._ImportParser(line)
833 duplicates = self.prop_overrides.intersection(overrides.keys())
834 if duplicates:
835 raise ValueError('prop {} is overridden multiple times'.format(
836 ','.join(duplicates)))
837 self.prop_overrides = self.prop_overrides.union(overrides.keys())
838 self.build_props.update(overrides)
839 elif "=" in line:
840 name, value = line.split("=", 1)
841 if name in self.prop_overrides:
842 raise ValueError('prop {} is set again after overridden by import '
843 'statement'.format(name))
844 self.build_props[name] = value
845
846 def _ImportParser(self, line):
847 """Parses the build prop in a given import statement."""
848
849 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400850 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000851 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700852
853 if len(tokens) == 3:
854 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
855 return {}
856
Tianjie Xu9afb2212020-05-10 21:48:15 +0000857 import_path = tokens[1]
858 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
859 raise ValueError('Unrecognized import path {}'.format(line))
860
861 # We only recognize a subset of import statement that the init process
862 # supports. And we can loose the restriction based on how the dynamic
863 # fingerprint is used in practice. The placeholder format should be
864 # ${placeholder}, and its value should be provided by the caller through
865 # the placeholder_values.
866 for prop, value in self.placeholder_values.items():
867 prop_place_holder = '${{{}}}'.format(prop)
868 if prop_place_holder in import_path:
869 import_path = import_path.replace(prop_place_holder, value)
870 if '$' in import_path:
871 logger.info('Unresolved place holder in import path %s', import_path)
872 return {}
873
874 import_path = import_path.replace('/{}'.format(self.partition),
875 self.partition.upper())
876 logger.info('Parsing build props override from %s', import_path)
877
878 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
879 d = LoadDictionaryFromLines(lines)
880 return {key: val for key, val in d.items()
881 if key in self.props_allow_override}
882
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000883 def GetProp(self, prop):
884 return self.build_props.get(prop)
885
886
Tianjie Xucfa86222016-03-07 16:31:19 -0800887def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
888 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700889 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700890 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700891 self.mount_point = mount_point
892 self.fs_type = fs_type
893 self.device = device
894 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700895 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700896 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700897
898 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800899 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700900 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700901 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700902 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700903
Tao Baod1de6f32017-03-01 16:38:48 -0800904 assert fstab_version == 2
905
906 d = {}
907 for line in data.split("\n"):
908 line = line.strip()
909 if not line or line.startswith("#"):
910 continue
911
912 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
913 pieces = line.split()
914 if len(pieces) != 5:
915 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
916
917 # Ignore entries that are managed by vold.
918 options = pieces[4]
919 if "voldmanaged=" in options:
920 continue
921
922 # It's a good line, parse it.
923 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700924 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800925 options = options.split(",")
926 for i in options:
927 if i.startswith("length="):
928 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700929 elif i == "slotselect":
930 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800931 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800932 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700933 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800934
Tao Baod1de6f32017-03-01 16:38:48 -0800935 mount_flags = pieces[3]
936 # Honor the SELinux context if present.
937 context = None
938 for i in mount_flags.split(","):
939 if i.startswith("context="):
940 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800941
Tao Baod1de6f32017-03-01 16:38:48 -0800942 mount_point = pieces[1]
943 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700944 device=pieces[0], length=length, context=context,
945 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800946
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700947 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700948 # system. Other areas assume system is always at "/system" so point /system
949 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700950 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800951 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700952 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700953 return d
954
955
Tao Bao765668f2019-10-04 22:03:00 -0700956def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
957 """Finds the path to recovery fstab and loads its contents."""
958 # recovery fstab is only meaningful when installing an update via recovery
959 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -0700960 if info_dict.get('ab_update') == 'true' and \
961 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -0700962 return None
963
964 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
965 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
966 # cases, since it may load the info_dict from an old build (e.g. when
967 # generating incremental OTAs from that build).
968 system_root_image = info_dict.get('system_root_image') == 'true'
969 if info_dict.get('no_recovery') != 'true':
970 recovery_fstab_path = 'RECOVERY/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 = 'RECOVERY/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 = 'RECOVERY/RAMDISK/etc/recovery.fstab'
978 return LoadRecoveryFSTab(
979 read_helper, info_dict['fstab_version'], recovery_fstab_path,
980 system_root_image)
981
982 if info_dict.get('recovery_as_boot') == 'true':
983 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
984 if isinstance(input_file, zipfile.ZipFile):
985 if recovery_fstab_path not in input_file.namelist():
986 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
987 else:
988 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
989 if not os.path.exists(path):
990 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
991 return LoadRecoveryFSTab(
992 read_helper, info_dict['fstab_version'], recovery_fstab_path,
993 system_root_image)
994
995 return None
996
997
Doug Zongker37974732010-09-16 17:44:38 -0700998def DumpInfoDict(d):
999 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001000 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001001
Dan Albert8b72aef2015-03-23 19:13:21 -07001002
Daniel Norman55417142019-11-25 16:04:36 -08001003def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001004 """Merges dynamic partition info variables.
1005
1006 Args:
1007 framework_dict: The dictionary of dynamic partition info variables from the
1008 partial framework target files.
1009 vendor_dict: The dictionary of dynamic partition info variables from the
1010 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001011
1012 Returns:
1013 The merged dynamic partition info dictionary.
1014 """
1015 merged_dict = {}
1016 # Partition groups and group sizes are defined by the vendor dict because
1017 # these values may vary for each board that uses a shared system image.
1018 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -08001019 framework_dynamic_partition_list = framework_dict.get(
1020 "dynamic_partition_list", "")
1021 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
1022 merged_dict["dynamic_partition_list"] = ("%s %s" % (
1023 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001024 for partition_group in merged_dict["super_partition_groups"].split(" "):
1025 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001026 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001027 if key not in vendor_dict:
1028 raise ValueError("Vendor dict does not contain required key %s." % key)
1029 merged_dict[key] = vendor_dict[key]
1030
1031 # Set the partition group's partition list using a concatenation of the
1032 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001033 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001034 merged_dict[key] = (
1035 "%s %s" %
1036 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301037
1038 # Pick virtual ab related flags from vendor dict, if defined.
1039 if "virtual_ab" in vendor_dict.keys():
Kelvin Zhang0876c412020-06-23 15:06:58 -04001040 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301041 if "virtual_ab_retrofit" in vendor_dict.keys():
Kelvin Zhang0876c412020-06-23 15:06:58 -04001042 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001043 return merged_dict
1044
1045
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001046def AppendAVBSigningArgs(cmd, partition):
1047 """Append signing arguments for avbtool."""
1048 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1049 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001050 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1051 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1052 if os.path.exists(new_key_path):
1053 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001054 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1055 if key_path and algorithm:
1056 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001057 avb_salt = OPTIONS.info_dict.get("avb_salt")
1058 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001059 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001060 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001061
1062
Tao Bao765668f2019-10-04 22:03:00 -07001063def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001064 """Returns the VBMeta arguments for partition.
1065
1066 It sets up the VBMeta argument by including the partition descriptor from the
1067 given 'image', or by configuring the partition as a chained partition.
1068
1069 Args:
1070 partition: The name of the partition (e.g. "system").
1071 image: The path to the partition image.
1072 info_dict: A dict returned by common.LoadInfoDict(). Will use
1073 OPTIONS.info_dict if None has been given.
1074
1075 Returns:
1076 A list of VBMeta arguments.
1077 """
1078 if info_dict is None:
1079 info_dict = OPTIONS.info_dict
1080
1081 # Check if chain partition is used.
1082 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001083 if not key_path:
1084 return ["--include_descriptors_from_image", image]
1085
1086 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1087 # into vbmeta.img. The recovery image will be configured on an independent
1088 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1089 # See details at
1090 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001091 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001092 return []
1093
1094 # Otherwise chain the partition into vbmeta.
1095 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1096 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001097
1098
Tao Bao02a08592018-07-22 12:40:45 -07001099def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1100 """Constructs and returns the arg to build or verify a chained partition.
1101
1102 Args:
1103 partition: The partition name.
1104 info_dict: The info dict to look up the key info and rollback index
1105 location.
1106 key: The key to be used for building or verifying the partition. Defaults to
1107 the key listed in info_dict.
1108
1109 Returns:
1110 A string of form "partition:rollback_index_location:key" that can be used to
1111 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001112 """
1113 if key is None:
1114 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001115 if key and not os.path.exists(key) and OPTIONS.search_path:
1116 new_key_path = os.path.join(OPTIONS.search_path, key)
1117 if os.path.exists(new_key_path):
1118 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001119 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001120 rollback_index_location = info_dict[
1121 "avb_" + partition + "_rollback_index_location"]
1122 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1123
1124
Tianjie20dd8f22020-04-19 15:51:16 -07001125def ConstructAftlMakeImageCommands(output_image):
1126 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001127
1128 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001129 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001130 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1131 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1132 'No AFTL manufacturer key provided.'
1133
1134 vbmeta_image = MakeTempFile()
1135 os.rename(output_image, vbmeta_image)
1136 build_info = BuildInfo(OPTIONS.info_dict)
1137 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001138 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001139 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001140 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001141 "--vbmeta_image_path", vbmeta_image,
1142 "--output", output_image,
1143 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001144 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001145 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1146 "--algorithm", "SHA256_RSA4096",
1147 "--padding", "4096"]
1148 if OPTIONS.aftl_signer_helper:
1149 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001150 return aftl_cmd
1151
1152
1153def AddAftlInclusionProof(output_image):
1154 """Appends the aftl inclusion proof to the vbmeta image."""
1155
1156 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001157 RunAndCheckOutput(aftl_cmd)
1158
1159 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1160 output_image, '--transparency_log_pub_keys',
1161 OPTIONS.aftl_key_path]
1162 RunAndCheckOutput(verify_cmd)
1163
1164
Daniel Norman276f0622019-07-26 14:13:51 -07001165def BuildVBMeta(image_path, partitions, name, needed_partitions):
1166 """Creates a VBMeta image.
1167
1168 It generates the requested VBMeta image. The requested image could be for
1169 top-level or chained VBMeta image, which is determined based on the name.
1170
1171 Args:
1172 image_path: The output path for the new VBMeta image.
1173 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001174 values. Only valid partition names are accepted, as partitions listed
1175 in common.AVB_PARTITIONS and custom partitions listed in
1176 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001177 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1178 needed_partitions: Partitions whose descriptors should be included into the
1179 generated VBMeta image.
1180
1181 Raises:
1182 AssertionError: On invalid input args.
1183 """
1184 avbtool = OPTIONS.info_dict["avb_avbtool"]
1185 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1186 AppendAVBSigningArgs(cmd, name)
1187
Hongguang Chenf23364d2020-04-27 18:36:36 -07001188 custom_partitions = OPTIONS.info_dict.get(
1189 "avb_custom_images_partition_list", "").strip().split()
1190
Daniel Norman276f0622019-07-26 14:13:51 -07001191 for partition, path in partitions.items():
1192 if partition not in needed_partitions:
1193 continue
1194 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001195 partition in AVB_VBMETA_PARTITIONS or
1196 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001197 'Unknown partition: {}'.format(partition)
1198 assert os.path.exists(path), \
1199 'Failed to find {} for {}'.format(path, partition)
1200 cmd.extend(GetAvbPartitionArg(partition, path))
1201
1202 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1203 if args and args.strip():
1204 split_args = shlex.split(args)
1205 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001206 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001207 # as a path relative to source tree, which may not be available at the
1208 # same location when running this script (we have the input target_files
1209 # zip only). For such cases, we additionally scan other locations (e.g.
1210 # IMAGES/, RADIO/, etc) before bailing out.
1211 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001212 chained_image = split_args[index + 1]
1213 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001214 continue
1215 found = False
1216 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1217 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001218 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001219 if os.path.exists(alt_path):
1220 split_args[index + 1] = alt_path
1221 found = True
1222 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001223 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001224 cmd.extend(split_args)
1225
1226 RunAndCheckOutput(cmd)
1227
Tianjie Xueaed60c2020-03-12 00:33:28 -07001228 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001229 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001230 AddAftlInclusionProof(image_path)
1231
Daniel Norman276f0622019-07-26 14:13:51 -07001232
J. Avila98cd4cc2020-06-10 20:09:10 +00001233def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001234 ramdisk_img = tempfile.NamedTemporaryFile()
1235
1236 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1237 cmd = ["mkbootfs", "-f", fs_config_file,
1238 os.path.join(sourcedir, "RAMDISK")]
1239 else:
1240 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1241 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001242 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001243 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001244 stdout=ramdisk_img.file.fileno())
1245 else:
1246 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001247
1248 p2.wait()
1249 p1.wait()
1250 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001251 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001252
1253 return ramdisk_img
1254
1255
Steve Muckle9793cf62020-04-08 18:27:00 -07001256def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001257 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001258 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001259
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001260 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001261 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1262 we are building a two-step special image (i.e. building a recovery image to
1263 be loaded into /boot in two-step OTAs).
1264
1265 Return the image data, or None if sourcedir does not appear to contains files
1266 for building the requested image.
1267 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001268
Steve Muckle9793cf62020-04-08 18:27:00 -07001269 # "boot" or "recovery", without extension.
1270 partition_name = os.path.basename(sourcedir).lower()
1271
1272 if partition_name == "recovery":
1273 kernel = "kernel"
1274 else:
1275 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001276 kernel = kernel.replace(".img", "")
Steve Muckle9793cf62020-04-08 18:27:00 -07001277 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001278 return None
1279
1280 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001281 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001282
Doug Zongkerd5131602012-08-02 14:46:42 -07001283 if info_dict is None:
1284 info_dict = OPTIONS.info_dict
1285
Doug Zongkereef39442009-04-02 12:14:19 -07001286 img = tempfile.NamedTemporaryFile()
1287
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001288 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001289 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1290 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001291
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001292 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1293 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1294
Steve Muckle9793cf62020-04-08 18:27:00 -07001295 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001296
Benoit Fradina45a8682014-07-14 21:00:43 +02001297 fn = os.path.join(sourcedir, "second")
1298 if os.access(fn, os.F_OK):
1299 cmd.append("--second")
1300 cmd.append(fn)
1301
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001302 fn = os.path.join(sourcedir, "dtb")
1303 if os.access(fn, os.F_OK):
1304 cmd.append("--dtb")
1305 cmd.append(fn)
1306
Doug Zongker171f1cd2009-06-15 22:36:37 -07001307 fn = os.path.join(sourcedir, "cmdline")
1308 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001309 cmd.append("--cmdline")
1310 cmd.append(open(fn).read().rstrip("\n"))
1311
1312 fn = os.path.join(sourcedir, "base")
1313 if os.access(fn, os.F_OK):
1314 cmd.append("--base")
1315 cmd.append(open(fn).read().rstrip("\n"))
1316
Ying Wang4de6b5b2010-08-25 14:29:34 -07001317 fn = os.path.join(sourcedir, "pagesize")
1318 if os.access(fn, os.F_OK):
1319 cmd.append("--pagesize")
1320 cmd.append(open(fn).read().rstrip("\n"))
1321
Steve Mucklef84668e2020-03-16 19:13:46 -07001322 if partition_name == "recovery":
1323 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301324 if not args:
1325 # Fall back to "mkbootimg_args" for recovery image
1326 # in case "recovery_mkbootimg_args" is not set.
1327 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001328 else:
1329 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001330 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001331 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001332
Tao Bao76def242017-11-21 09:25:31 -08001333 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001334 if args and args.strip():
1335 cmd.extend(shlex.split(args))
1336
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001337 if has_ramdisk:
1338 cmd.extend(["--ramdisk", ramdisk_img.name])
1339
Tao Baod95e9fd2015-03-29 23:07:41 -07001340 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001341 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001342 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001343 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001344 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001345 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001346
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001347 if partition_name == "recovery":
1348 if info_dict.get("include_recovery_dtbo") == "true":
1349 fn = os.path.join(sourcedir, "recovery_dtbo")
1350 cmd.extend(["--recovery_dtbo", fn])
1351 if info_dict.get("include_recovery_acpio") == "true":
1352 fn = os.path.join(sourcedir, "recovery_acpio")
1353 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001354
Tao Bao986ee862018-10-04 15:46:16 -07001355 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001356
Tao Bao76def242017-11-21 09:25:31 -08001357 if (info_dict.get("boot_signer") == "true" and
1358 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001359 # Hard-code the path as "/boot" for two-step special recovery image (which
1360 # will be loaded into /boot during the two-step OTA).
1361 if two_step_image:
1362 path = "/boot"
1363 else:
Tao Baobf70c312017-07-11 17:27:55 -07001364 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001365 cmd = [OPTIONS.boot_signer_path]
1366 cmd.extend(OPTIONS.boot_signer_args)
1367 cmd.extend([path, img.name,
1368 info_dict["verity_key"] + ".pk8",
1369 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001370 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001371
Tao Baod95e9fd2015-03-29 23:07:41 -07001372 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001373 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001374 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001375 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001376 # We have switched from the prebuilt futility binary to using the tool
1377 # (futility-host) built from the source. Override the setting in the old
1378 # TF.zip.
1379 futility = info_dict["futility"]
1380 if futility.startswith("prebuilts/"):
1381 futility = "futility-host"
1382 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001383 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001384 info_dict["vboot_key"] + ".vbprivk",
1385 info_dict["vboot_subkey"] + ".vbprivk",
1386 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001387 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001388 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001389
Tao Baof3282b42015-04-01 11:21:55 -07001390 # Clean up the temp files.
1391 img_unsigned.close()
1392 img_keyblock.close()
1393
David Zeuthen8fecb282017-12-01 16:24:01 -05001394 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001395 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001396 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001397 if partition_name == "recovery":
1398 part_size = info_dict["recovery_size"]
1399 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001400 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001401 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001402 "--partition_size", str(part_size), "--partition_name",
1403 partition_name]
1404 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001405 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001406 if args and args.strip():
1407 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001408 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001409
1410 img.seek(os.SEEK_SET, 0)
1411 data = img.read()
1412
1413 if has_ramdisk:
1414 ramdisk_img.close()
1415 img.close()
1416
1417 return data
1418
1419
Doug Zongkerd5131602012-08-02 14:46:42 -07001420def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001421 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001422 """Return a File object with the desired bootable image.
1423
1424 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1425 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1426 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001427
Doug Zongker55d93282011-01-25 17:03:34 -08001428 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1429 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001430 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001431 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001432
1433 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1434 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001435 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001436 return File.FromLocalFile(name, prebuilt_path)
1437
Tao Bao32fcdab2018-10-12 10:30:39 -07001438 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001439
1440 if info_dict is None:
1441 info_dict = OPTIONS.info_dict
1442
1443 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001444 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1445 # for recovery.
1446 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1447 prebuilt_name != "boot.img" or
1448 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001449
Doug Zongker6f1d0312014-08-22 08:07:12 -07001450 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001451 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001452 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001453 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001454 if data:
1455 return File(name, data)
1456 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001457
Doug Zongkereef39442009-04-02 12:14:19 -07001458
Steve Mucklee1b10862019-07-10 10:49:37 -07001459def _BuildVendorBootImage(sourcedir, info_dict=None):
1460 """Build a vendor boot image from the specified sourcedir.
1461
1462 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1463 turn them into a vendor boot image.
1464
1465 Return the image data, or None if sourcedir does not appear to contains files
1466 for building the requested image.
1467 """
1468
1469 if info_dict is None:
1470 info_dict = OPTIONS.info_dict
1471
1472 img = tempfile.NamedTemporaryFile()
1473
J. Avila98cd4cc2020-06-10 20:09:10 +00001474 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1475 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001476
1477 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1478 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1479
1480 cmd = [mkbootimg]
1481
1482 fn = os.path.join(sourcedir, "dtb")
1483 if os.access(fn, os.F_OK):
1484 cmd.append("--dtb")
1485 cmd.append(fn)
1486
1487 fn = os.path.join(sourcedir, "vendor_cmdline")
1488 if os.access(fn, os.F_OK):
1489 cmd.append("--vendor_cmdline")
1490 cmd.append(open(fn).read().rstrip("\n"))
1491
1492 fn = os.path.join(sourcedir, "base")
1493 if os.access(fn, os.F_OK):
1494 cmd.append("--base")
1495 cmd.append(open(fn).read().rstrip("\n"))
1496
1497 fn = os.path.join(sourcedir, "pagesize")
1498 if os.access(fn, os.F_OK):
1499 cmd.append("--pagesize")
1500 cmd.append(open(fn).read().rstrip("\n"))
1501
1502 args = info_dict.get("mkbootimg_args")
1503 if args and args.strip():
1504 cmd.extend(shlex.split(args))
1505
1506 args = info_dict.get("mkbootimg_version_args")
1507 if args and args.strip():
1508 cmd.extend(shlex.split(args))
1509
1510 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1511 cmd.extend(["--vendor_boot", img.name])
1512
1513 RunAndCheckOutput(cmd)
1514
1515 # AVB: if enabled, calculate and add hash.
1516 if info_dict.get("avb_enable") == "true":
1517 avbtool = info_dict["avb_avbtool"]
1518 part_size = info_dict["vendor_boot_size"]
1519 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001520 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001521 AppendAVBSigningArgs(cmd, "vendor_boot")
1522 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1523 if args and args.strip():
1524 cmd.extend(shlex.split(args))
1525 RunAndCheckOutput(cmd)
1526
1527 img.seek(os.SEEK_SET, 0)
1528 data = img.read()
1529
1530 ramdisk_img.close()
1531 img.close()
1532
1533 return data
1534
1535
1536def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1537 info_dict=None):
1538 """Return a File object with the desired vendor boot image.
1539
1540 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1541 the source files in 'unpack_dir'/'tree_subdir'."""
1542
1543 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1544 if os.path.exists(prebuilt_path):
1545 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1546 return File.FromLocalFile(name, prebuilt_path)
1547
1548 logger.info("building image from target_files %s...", tree_subdir)
1549
1550 if info_dict is None:
1551 info_dict = OPTIONS.info_dict
1552
Kelvin Zhang0876c412020-06-23 15:06:58 -04001553 data = _BuildVendorBootImage(
1554 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001555 if data:
1556 return File(name, data)
1557 return None
1558
1559
Narayan Kamatha07bf042017-08-14 14:49:21 +01001560def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001561 """Gunzips the given gzip compressed file to a given output file."""
1562 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001563 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001564 shutil.copyfileobj(in_file, out_file)
1565
1566
Tao Bao0ff15de2019-03-20 11:26:06 -07001567def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001568 """Unzips the archive to the given directory.
1569
1570 Args:
1571 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001572 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001573 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1574 archvie. Non-matching patterns will be filtered out. If there's no match
1575 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001576 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001577 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001578 if patterns is not None:
1579 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001580 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001581 names = input_zip.namelist()
1582 filtered = [
1583 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1584
1585 # There isn't any matching files. Don't unzip anything.
1586 if not filtered:
1587 return
1588 cmd.extend(filtered)
1589
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001590 RunAndCheckOutput(cmd)
1591
1592
Doug Zongker75f17362009-12-08 13:46:44 -08001593def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001594 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001595
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001596 Args:
1597 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1598 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1599
1600 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1601 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001602
Tao Bao1c830bf2017-12-25 10:43:47 -08001603 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001604 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001605 """
Doug Zongkereef39442009-04-02 12:14:19 -07001606
Tao Bao1c830bf2017-12-25 10:43:47 -08001607 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001608 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1609 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001610 UnzipToDir(m.group(1), tmp, pattern)
1611 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001612 filename = m.group(1)
1613 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001614 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001615
Tao Baodba59ee2018-01-09 13:21:02 -08001616 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001617
1618
Yifan Hong8a66a712019-04-04 15:37:57 -07001619def GetUserImage(which, tmpdir, input_zip,
1620 info_dict=None,
1621 allow_shared_blocks=None,
1622 hashtree_info_generator=None,
1623 reset_file_map=False):
1624 """Returns an Image object suitable for passing to BlockImageDiff.
1625
1626 This function loads the specified image from the given path. If the specified
1627 image is sparse, it also performs additional processing for OTA purpose. For
1628 example, it always adds block 0 to clobbered blocks list. It also detects
1629 files that cannot be reconstructed from the block list, for whom we should
1630 avoid applying imgdiff.
1631
1632 Args:
1633 which: The partition name.
1634 tmpdir: The directory that contains the prebuilt image and block map file.
1635 input_zip: The target-files ZIP archive.
1636 info_dict: The dict to be looked up for relevant info.
1637 allow_shared_blocks: If image is sparse, whether having shared blocks is
1638 allowed. If none, it is looked up from info_dict.
1639 hashtree_info_generator: If present and image is sparse, generates the
1640 hashtree_info for this sparse image.
1641 reset_file_map: If true and image is sparse, reset file map before returning
1642 the image.
1643 Returns:
1644 A Image object. If it is a sparse image and reset_file_map is False, the
1645 image will have file_map info loaded.
1646 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001647 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001648 info_dict = LoadInfoDict(input_zip)
1649
1650 is_sparse = info_dict.get("extfs_sparse_flag")
1651
1652 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1653 # shared blocks (i.e. some blocks will show up in multiple files' block
1654 # list). We can only allocate such shared blocks to the first "owner", and
1655 # disable imgdiff for all later occurrences.
1656 if allow_shared_blocks is None:
1657 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1658
1659 if is_sparse:
1660 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1661 hashtree_info_generator)
1662 if reset_file_map:
1663 img.ResetFileMap()
1664 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001665 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001666
1667
1668def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1669 """Returns a Image object suitable for passing to BlockImageDiff.
1670
1671 This function loads the specified non-sparse image from the given path.
1672
1673 Args:
1674 which: The partition name.
1675 tmpdir: The directory that contains the prebuilt image and block map file.
1676 Returns:
1677 A Image object.
1678 """
1679 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1680 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1681
1682 # The image and map files must have been created prior to calling
1683 # ota_from_target_files.py (since LMP).
1684 assert os.path.exists(path) and os.path.exists(mappath)
1685
Tianjie Xu41976c72019-07-03 13:57:01 -07001686 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1687
Yifan Hong8a66a712019-04-04 15:37:57 -07001688
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001689def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1690 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001691 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1692
1693 This function loads the specified sparse image from the given path, and
1694 performs additional processing for OTA purpose. For example, it always adds
1695 block 0 to clobbered blocks list. It also detects files that cannot be
1696 reconstructed from the block list, for whom we should avoid applying imgdiff.
1697
1698 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001699 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001700 tmpdir: The directory that contains the prebuilt image and block map file.
1701 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001702 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001703 hashtree_info_generator: If present, generates the hashtree_info for this
1704 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001705 Returns:
1706 A SparseImage object, with file_map info loaded.
1707 """
Tao Baoc765cca2018-01-31 17:32:40 -08001708 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1709 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1710
1711 # The image and map files must have been created prior to calling
1712 # ota_from_target_files.py (since LMP).
1713 assert os.path.exists(path) and os.path.exists(mappath)
1714
1715 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1716 # it to clobbered_blocks so that it will be written to the target
1717 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1718 clobbered_blocks = "0"
1719
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001720 image = sparse_img.SparseImage(
1721 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1722 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001723
1724 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1725 # if they contain all zeros. We can't reconstruct such a file from its block
1726 # list. Tag such entries accordingly. (Bug: 65213616)
1727 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001728 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001729 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001730 continue
1731
Tom Cherryd14b8952018-08-09 14:26:00 -07001732 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1733 # filename listed in system.map may contain an additional leading slash
1734 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1735 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001736 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001737
Tom Cherryd14b8952018-08-09 14:26:00 -07001738 # Special handling another case, where files not under /system
1739 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001740 if which == 'system' and not arcname.startswith('SYSTEM'):
1741 arcname = 'ROOT/' + arcname
1742
1743 assert arcname in input_zip.namelist(), \
1744 "Failed to find the ZIP entry for {}".format(entry)
1745
Tao Baoc765cca2018-01-31 17:32:40 -08001746 info = input_zip.getinfo(arcname)
1747 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001748
1749 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001750 # image, check the original block list to determine its completeness. Note
1751 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001752 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001753 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001754
Tao Baoc765cca2018-01-31 17:32:40 -08001755 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1756 ranges.extra['incomplete'] = True
1757
1758 return image
1759
1760
Doug Zongkereef39442009-04-02 12:14:19 -07001761def GetKeyPasswords(keylist):
1762 """Given a list of keys, prompt the user to enter passwords for
1763 those which require them. Return a {key: password} dict. password
1764 will be None if the key has no password."""
1765
Doug Zongker8ce7c252009-05-22 13:34:54 -07001766 no_passwords = []
1767 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001768 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001769 devnull = open("/dev/null", "w+b")
1770 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001771 # We don't need a password for things that aren't really keys.
1772 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001773 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001774 continue
1775
T.R. Fullhart37e10522013-03-18 10:31:26 -07001776 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001777 "-inform", "DER", "-nocrypt"],
1778 stdin=devnull.fileno(),
1779 stdout=devnull.fileno(),
1780 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001781 p.communicate()
1782 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001783 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001784 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001785 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001786 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1787 "-inform", "DER", "-passin", "pass:"],
1788 stdin=devnull.fileno(),
1789 stdout=devnull.fileno(),
1790 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001791 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001792 if p.returncode == 0:
1793 # Encrypted key with empty string as password.
1794 key_passwords[k] = ''
1795 elif stderr.startswith('Error decrypting key'):
1796 # Definitely encrypted key.
1797 # It would have said "Error reading key" if it didn't parse correctly.
1798 need_passwords.append(k)
1799 else:
1800 # Potentially, a type of key that openssl doesn't understand.
1801 # We'll let the routines in signapk.jar handle it.
1802 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001803 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001804
T.R. Fullhart37e10522013-03-18 10:31:26 -07001805 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001806 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001807 return key_passwords
1808
1809
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001810def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001811 """Gets the minSdkVersion declared in the APK.
1812
changho.shin0f125362019-07-08 10:59:00 +09001813 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001814 This can be both a decimal number (API Level) or a codename.
1815
1816 Args:
1817 apk_name: The APK filename.
1818
1819 Returns:
1820 The parsed SDK version string.
1821
1822 Raises:
1823 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001824 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001825 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001826 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001827 stderr=subprocess.PIPE)
1828 stdoutdata, stderrdata = proc.communicate()
1829 if proc.returncode != 0:
1830 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001831 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001832 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001833
Tao Baof47bf0f2018-03-21 23:28:51 -07001834 for line in stdoutdata.split("\n"):
1835 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001836 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1837 if m:
1838 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001839 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001840
1841
1842def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001843 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001844
Tao Baof47bf0f2018-03-21 23:28:51 -07001845 If minSdkVersion is set to a codename, it is translated to a number using the
1846 provided map.
1847
1848 Args:
1849 apk_name: The APK filename.
1850
1851 Returns:
1852 The parsed SDK version number.
1853
1854 Raises:
1855 ExternalError: On failing to get the min SDK version number.
1856 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001857 version = GetMinSdkVersion(apk_name)
1858 try:
1859 return int(version)
1860 except ValueError:
1861 # Not a decimal number. Codename?
1862 if version in codename_to_api_level_map:
1863 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04001864 raise ExternalError(
1865 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1866 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001867
1868
1869def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001870 codename_to_api_level_map=None, whole_file=False,
1871 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001872 """Sign the input_name zip/jar/apk, producing output_name. Use the
1873 given key and password (the latter may be None if the key does not
1874 have a password.
1875
Doug Zongker951495f2009-08-14 12:44:19 -07001876 If whole_file is true, use the "-w" option to SignApk to embed a
1877 signature that covers the whole file in the archive comment of the
1878 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001879
1880 min_api_level is the API Level (int) of the oldest platform this file may end
1881 up on. If not specified for an APK, the API Level is obtained by interpreting
1882 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1883
1884 codename_to_api_level_map is needed to translate the codename which may be
1885 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001886
1887 Caller may optionally specify extra args to be passed to SignApk, which
1888 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001889 """
Tao Bao76def242017-11-21 09:25:31 -08001890 if codename_to_api_level_map is None:
1891 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001892 if extra_signapk_args is None:
1893 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001894
Alex Klyubin9667b182015-12-10 13:38:50 -08001895 java_library_path = os.path.join(
1896 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1897
Tao Baoe95540e2016-11-08 12:08:53 -08001898 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1899 ["-Djava.library.path=" + java_library_path,
1900 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001901 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001902 if whole_file:
1903 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001904
1905 min_sdk_version = min_api_level
1906 if min_sdk_version is None:
1907 if not whole_file:
1908 min_sdk_version = GetMinSdkVersionInt(
1909 input_name, codename_to_api_level_map)
1910 if min_sdk_version is not None:
1911 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1912
T.R. Fullhart37e10522013-03-18 10:31:26 -07001913 cmd.extend([key + OPTIONS.public_key_suffix,
1914 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001915 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001916
Tao Bao73dd4f42018-10-04 16:25:33 -07001917 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001918 if password is not None:
1919 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001920 stdoutdata, _ = proc.communicate(password)
1921 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001922 raise ExternalError(
1923 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001924 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001925
Doug Zongkereef39442009-04-02 12:14:19 -07001926
Doug Zongker37974732010-09-16 17:44:38 -07001927def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001928 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001929
Tao Bao9dd909e2017-11-14 11:27:32 -08001930 For non-AVB images, raise exception if the data is too big. Print a warning
1931 if the data is nearing the maximum size.
1932
1933 For AVB images, the actual image size should be identical to the limit.
1934
1935 Args:
1936 data: A string that contains all the data for the partition.
1937 target: The partition name. The ".img" suffix is optional.
1938 info_dict: The dict to be looked up for relevant info.
1939 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001940 if target.endswith(".img"):
1941 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001942 mount_point = "/" + target
1943
Ying Wangf8824af2014-06-03 14:07:27 -07001944 fs_type = None
1945 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001946 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001947 if mount_point == "/userdata":
1948 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001949 p = info_dict["fstab"][mount_point]
1950 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001951 device = p.device
1952 if "/" in device:
1953 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001954 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001955 if not fs_type or not limit:
1956 return
Doug Zongkereef39442009-04-02 12:14:19 -07001957
Andrew Boie0f9aec82012-02-14 09:32:52 -08001958 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001959 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1960 # path.
1961 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1962 if size != limit:
1963 raise ExternalError(
1964 "Mismatching image size for %s: expected %d actual %d" % (
1965 target, limit, size))
1966 else:
1967 pct = float(size) * 100.0 / limit
1968 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1969 if pct >= 99.0:
1970 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04001971
1972 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001973 logger.warning("\n WARNING: %s\n", msg)
1974 else:
1975 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001976
1977
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001978def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001979 """Parses the APK certs info from a given target-files zip.
1980
1981 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1982 tuple with the following elements: (1) a dictionary that maps packages to
1983 certs (based on the "certificate" and "private_key" attributes in the file;
1984 (2) a string representing the extension of compressed APKs in the target files
1985 (e.g ".gz", ".bro").
1986
1987 Args:
1988 tf_zip: The input target_files ZipFile (already open).
1989
1990 Returns:
1991 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1992 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1993 no compressed APKs.
1994 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001995 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001996 compressed_extension = None
1997
Tao Bao0f990332017-09-08 19:02:54 -07001998 # META/apkcerts.txt contains the info for _all_ the packages known at build
1999 # time. Filter out the ones that are not installed.
2000 installed_files = set()
2001 for name in tf_zip.namelist():
2002 basename = os.path.basename(name)
2003 if basename:
2004 installed_files.add(basename)
2005
Tao Baoda30cfa2017-12-01 16:19:46 -08002006 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002007 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002008 if not line:
2009 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002010 m = re.match(
2011 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002012 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2013 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002014 line)
2015 if not m:
2016 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002017
Tao Bao818ddf52018-01-05 11:17:34 -08002018 matches = m.groupdict()
2019 cert = matches["CERT"]
2020 privkey = matches["PRIVKEY"]
2021 name = matches["NAME"]
2022 this_compressed_extension = matches["COMPRESSED"]
2023
2024 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2025 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2026 if cert in SPECIAL_CERT_STRINGS and not privkey:
2027 certmap[name] = cert
2028 elif (cert.endswith(OPTIONS.public_key_suffix) and
2029 privkey.endswith(OPTIONS.private_key_suffix) and
2030 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2031 certmap[name] = cert[:-public_key_suffix_len]
2032 else:
2033 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2034
2035 if not this_compressed_extension:
2036 continue
2037
2038 # Only count the installed files.
2039 filename = name + '.' + this_compressed_extension
2040 if filename not in installed_files:
2041 continue
2042
2043 # Make sure that all the values in the compression map have the same
2044 # extension. We don't support multiple compression methods in the same
2045 # system image.
2046 if compressed_extension:
2047 if this_compressed_extension != compressed_extension:
2048 raise ValueError(
2049 "Multiple compressed extensions: {} vs {}".format(
2050 compressed_extension, this_compressed_extension))
2051 else:
2052 compressed_extension = this_compressed_extension
2053
2054 return (certmap,
2055 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002056
2057
Doug Zongkereef39442009-04-02 12:14:19 -07002058COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002059Global options
2060
2061 -p (--path) <dir>
2062 Prepend <dir>/bin to the list of places to search for binaries run by this
2063 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002064
Doug Zongker05d3dea2009-06-22 11:32:31 -07002065 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002066 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002067
Tao Bao30df8b42018-04-23 15:32:53 -07002068 -x (--extra) <key=value>
2069 Add a key/value pair to the 'extras' dict, which device-specific extension
2070 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002071
Doug Zongkereef39442009-04-02 12:14:19 -07002072 -v (--verbose)
2073 Show command lines being executed.
2074
2075 -h (--help)
2076 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002077
2078 --logfile <file>
2079 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002080"""
2081
Kelvin Zhang0876c412020-06-23 15:06:58 -04002082
Doug Zongkereef39442009-04-02 12:14:19 -07002083def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002084 print(docstring.rstrip("\n"))
2085 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002086
2087
2088def ParseOptions(argv,
2089 docstring,
2090 extra_opts="", extra_long_opts=(),
2091 extra_option_handler=None):
2092 """Parse the options in argv and return any arguments that aren't
2093 flags. docstring is the calling module's docstring, to be displayed
2094 for errors and -h. extra_opts and extra_long_opts are for flags
2095 defined by the caller, which are processed by passing them to
2096 extra_option_handler."""
2097
2098 try:
2099 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002100 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002101 ["help", "verbose", "path=", "signapk_path=",
2102 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002103 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002104 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2105 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002106 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2107 "aftl_key_path=", "aftl_manufacturer_key_path=",
2108 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002109 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002110 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002111 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002112 sys.exit(2)
2113
Doug Zongkereef39442009-04-02 12:14:19 -07002114 for o, a in opts:
2115 if o in ("-h", "--help"):
2116 Usage(docstring)
2117 sys.exit()
2118 elif o in ("-v", "--verbose"):
2119 OPTIONS.verbose = True
2120 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002121 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002122 elif o in ("--signapk_path",):
2123 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002124 elif o in ("--signapk_shared_library_path",):
2125 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002126 elif o in ("--extra_signapk_args",):
2127 OPTIONS.extra_signapk_args = shlex.split(a)
2128 elif o in ("--java_path",):
2129 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002130 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002131 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002132 elif o in ("--android_jar_path",):
2133 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002134 elif o in ("--public_key_suffix",):
2135 OPTIONS.public_key_suffix = a
2136 elif o in ("--private_key_suffix",):
2137 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002138 elif o in ("--boot_signer_path",):
2139 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002140 elif o in ("--boot_signer_args",):
2141 OPTIONS.boot_signer_args = shlex.split(a)
2142 elif o in ("--verity_signer_path",):
2143 OPTIONS.verity_signer_path = a
2144 elif o in ("--verity_signer_args",):
2145 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002146 elif o in ("--aftl_tool_path",):
2147 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002148 elif o in ("--aftl_server",):
2149 OPTIONS.aftl_server = a
2150 elif o in ("--aftl_key_path",):
2151 OPTIONS.aftl_key_path = a
2152 elif o in ("--aftl_manufacturer_key_path",):
2153 OPTIONS.aftl_manufacturer_key_path = a
2154 elif o in ("--aftl_signer_helper",):
2155 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002156 elif o in ("-s", "--device_specific"):
2157 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002158 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002159 key, value = a.split("=", 1)
2160 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002161 elif o in ("--logfile",):
2162 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002163 else:
2164 if extra_option_handler is None or not extra_option_handler(o, a):
2165 assert False, "unknown option \"%s\"" % (o,)
2166
Doug Zongker85448772014-09-09 14:59:20 -07002167 if OPTIONS.search_path:
2168 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2169 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002170
2171 return args
2172
2173
Tao Bao4c851b12016-09-19 13:54:38 -07002174def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002175 """Make a temp file and add it to the list of things to be deleted
2176 when Cleanup() is called. Return the filename."""
2177 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2178 os.close(fd)
2179 OPTIONS.tempfiles.append(fn)
2180 return fn
2181
2182
Tao Bao1c830bf2017-12-25 10:43:47 -08002183def MakeTempDir(prefix='tmp', suffix=''):
2184 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2185
2186 Returns:
2187 The absolute pathname of the new directory.
2188 """
2189 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2190 OPTIONS.tempfiles.append(dir_name)
2191 return dir_name
2192
2193
Doug Zongkereef39442009-04-02 12:14:19 -07002194def Cleanup():
2195 for i in OPTIONS.tempfiles:
2196 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002197 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002198 else:
2199 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002200 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002201
2202
2203class PasswordManager(object):
2204 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002205 self.editor = os.getenv("EDITOR")
2206 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002207
2208 def GetPasswords(self, items):
2209 """Get passwords corresponding to each string in 'items',
2210 returning a dict. (The dict may have keys in addition to the
2211 values in 'items'.)
2212
2213 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2214 user edit that file to add more needed passwords. If no editor is
2215 available, or $ANDROID_PW_FILE isn't define, prompts the user
2216 interactively in the ordinary way.
2217 """
2218
2219 current = self.ReadFile()
2220
2221 first = True
2222 while True:
2223 missing = []
2224 for i in items:
2225 if i not in current or not current[i]:
2226 missing.append(i)
2227 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002228 if not missing:
2229 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002230
2231 for i in missing:
2232 current[i] = ""
2233
2234 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002235 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002236 if sys.version_info[0] >= 3:
2237 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002238 answer = raw_input("try to edit again? [y]> ").strip()
2239 if answer and answer[0] not in 'yY':
2240 raise RuntimeError("key passwords unavailable")
2241 first = False
2242
2243 current = self.UpdateAndReadFile(current)
2244
Kelvin Zhang0876c412020-06-23 15:06:58 -04002245 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002246 """Prompt the user to enter a value (password) for each key in
2247 'current' whose value is fales. Returns a new dict with all the
2248 values.
2249 """
2250 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002251 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002252 if v:
2253 result[k] = v
2254 else:
2255 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002256 result[k] = getpass.getpass(
2257 "Enter password for %s key> " % k).strip()
2258 if result[k]:
2259 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002260 return result
2261
2262 def UpdateAndReadFile(self, current):
2263 if not self.editor or not self.pwfile:
2264 return self.PromptResult(current)
2265
2266 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002267 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002268 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2269 f.write("# (Additional spaces are harmless.)\n\n")
2270
2271 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002272 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002273 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002274 f.write("[[[ %s ]]] %s\n" % (v, k))
2275 if not v and first_line is None:
2276 # position cursor on first line with no password.
2277 first_line = i + 4
2278 f.close()
2279
Tao Bao986ee862018-10-04 15:46:16 -07002280 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002281
2282 return self.ReadFile()
2283
2284 def ReadFile(self):
2285 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002286 if self.pwfile is None:
2287 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002288 try:
2289 f = open(self.pwfile, "r")
2290 for line in f:
2291 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002292 if not line or line[0] == '#':
2293 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002294 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2295 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002296 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002297 else:
2298 result[m.group(2)] = m.group(1)
2299 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002300 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002301 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002302 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002303 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002304
2305
Dan Albert8e0178d2015-01-27 15:53:15 -08002306def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2307 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002308
2309 # http://b/18015246
2310 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2311 # for files larger than 2GiB. We can work around this by adjusting their
2312 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2313 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2314 # it isn't clear to me exactly what circumstances cause this).
2315 # `zipfile.write()` must be used directly to work around this.
2316 #
2317 # This mess can be avoided if we port to python3.
2318 saved_zip64_limit = zipfile.ZIP64_LIMIT
2319 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2320
2321 if compress_type is None:
2322 compress_type = zip_file.compression
2323 if arcname is None:
2324 arcname = filename
2325
2326 saved_stat = os.stat(filename)
2327
2328 try:
2329 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2330 # file to be zipped and reset it when we're done.
2331 os.chmod(filename, perms)
2332
2333 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002334 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2335 # intentional. zip stores datetimes in local time without a time zone
2336 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2337 # in the zip archive.
2338 local_epoch = datetime.datetime.fromtimestamp(0)
2339 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002340 os.utime(filename, (timestamp, timestamp))
2341
2342 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2343 finally:
2344 os.chmod(filename, saved_stat.st_mode)
2345 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2346 zipfile.ZIP64_LIMIT = saved_zip64_limit
2347
2348
Tao Bao58c1b962015-05-20 09:32:18 -07002349def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002350 compress_type=None):
2351 """Wrap zipfile.writestr() function to work around the zip64 limit.
2352
2353 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2354 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2355 when calling crc32(bytes).
2356
2357 But it still works fine to write a shorter string into a large zip file.
2358 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2359 when we know the string won't be too long.
2360 """
2361
2362 saved_zip64_limit = zipfile.ZIP64_LIMIT
2363 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2364
2365 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2366 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002367 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002368 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002369 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002370 else:
Tao Baof3282b42015-04-01 11:21:55 -07002371 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002372 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2373 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2374 # such a case (since
2375 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2376 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2377 # permission bits. We follow the logic in Python 3 to get consistent
2378 # behavior between using the two versions.
2379 if not zinfo.external_attr:
2380 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002381
2382 # If compress_type is given, it overrides the value in zinfo.
2383 if compress_type is not None:
2384 zinfo.compress_type = compress_type
2385
Tao Bao58c1b962015-05-20 09:32:18 -07002386 # If perms is given, it has a priority.
2387 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002388 # If perms doesn't set the file type, mark it as a regular file.
2389 if perms & 0o770000 == 0:
2390 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002391 zinfo.external_attr = perms << 16
2392
Tao Baof3282b42015-04-01 11:21:55 -07002393 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002394 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2395
Dan Albert8b72aef2015-03-23 19:13:21 -07002396 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002397 zipfile.ZIP64_LIMIT = saved_zip64_limit
2398
2399
Tao Bao89d7ab22017-12-14 17:05:33 -08002400def ZipDelete(zip_filename, entries):
2401 """Deletes entries from a ZIP file.
2402
2403 Since deleting entries from a ZIP file is not supported, it shells out to
2404 'zip -d'.
2405
2406 Args:
2407 zip_filename: The name of the ZIP file.
2408 entries: The name of the entry, or the list of names to be deleted.
2409
2410 Raises:
2411 AssertionError: In case of non-zero return from 'zip'.
2412 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002413 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002414 entries = [entries]
2415 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002416 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002417
2418
Tao Baof3282b42015-04-01 11:21:55 -07002419def ZipClose(zip_file):
2420 # http://b/18015246
2421 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2422 # central directory.
2423 saved_zip64_limit = zipfile.ZIP64_LIMIT
2424 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2425
2426 zip_file.close()
2427
2428 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002429
2430
2431class DeviceSpecificParams(object):
2432 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002433
Doug Zongker05d3dea2009-06-22 11:32:31 -07002434 def __init__(self, **kwargs):
2435 """Keyword arguments to the constructor become attributes of this
2436 object, which is passed to all functions in the device-specific
2437 module."""
Tao Bao38884282019-07-10 22:20:56 -07002438 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002439 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002440 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002441
2442 if self.module is None:
2443 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002444 if not path:
2445 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002446 try:
2447 if os.path.isdir(path):
2448 info = imp.find_module("releasetools", [path])
2449 else:
2450 d, f = os.path.split(path)
2451 b, x = os.path.splitext(f)
2452 if x == ".py":
2453 f = b
2454 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002455 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002456 self.module = imp.load_module("device_specific", *info)
2457 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002458 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002459
2460 def _DoCall(self, function_name, *args, **kwargs):
2461 """Call the named function in the device-specific module, passing
2462 the given args and kwargs. The first argument to the call will be
2463 the DeviceSpecific object itself. If there is no module, or the
2464 module does not define the function, return the value of the
2465 'default' kwarg (which itself defaults to None)."""
2466 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002467 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002468 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2469
2470 def FullOTA_Assertions(self):
2471 """Called after emitting the block of assertions at the top of a
2472 full OTA package. Implementations can add whatever additional
2473 assertions they like."""
2474 return self._DoCall("FullOTA_Assertions")
2475
Doug Zongkere5ff5902012-01-17 10:55:37 -08002476 def FullOTA_InstallBegin(self):
2477 """Called at the start of full OTA installation."""
2478 return self._DoCall("FullOTA_InstallBegin")
2479
Yifan Hong10c530d2018-12-27 17:34:18 -08002480 def FullOTA_GetBlockDifferences(self):
2481 """Called during full OTA installation and verification.
2482 Implementation should return a list of BlockDifference objects describing
2483 the update on each additional partitions.
2484 """
2485 return self._DoCall("FullOTA_GetBlockDifferences")
2486
Doug Zongker05d3dea2009-06-22 11:32:31 -07002487 def FullOTA_InstallEnd(self):
2488 """Called at the end of full OTA installation; typically this is
2489 used to install the image for the device's baseband processor."""
2490 return self._DoCall("FullOTA_InstallEnd")
2491
2492 def IncrementalOTA_Assertions(self):
2493 """Called after emitting the block of assertions at the top of an
2494 incremental OTA package. Implementations can add whatever
2495 additional assertions they like."""
2496 return self._DoCall("IncrementalOTA_Assertions")
2497
Doug Zongkere5ff5902012-01-17 10:55:37 -08002498 def IncrementalOTA_VerifyBegin(self):
2499 """Called at the start of the verification phase of incremental
2500 OTA installation; additional checks can be placed here to abort
2501 the script before any changes are made."""
2502 return self._DoCall("IncrementalOTA_VerifyBegin")
2503
Doug Zongker05d3dea2009-06-22 11:32:31 -07002504 def IncrementalOTA_VerifyEnd(self):
2505 """Called at the end of the verification phase of incremental OTA
2506 installation; additional checks can be placed here to abort the
2507 script before any changes are made."""
2508 return self._DoCall("IncrementalOTA_VerifyEnd")
2509
Doug Zongkere5ff5902012-01-17 10:55:37 -08002510 def IncrementalOTA_InstallBegin(self):
2511 """Called at the start of incremental OTA installation (after
2512 verification is complete)."""
2513 return self._DoCall("IncrementalOTA_InstallBegin")
2514
Yifan Hong10c530d2018-12-27 17:34:18 -08002515 def IncrementalOTA_GetBlockDifferences(self):
2516 """Called during incremental OTA installation and verification.
2517 Implementation should return a list of BlockDifference objects describing
2518 the update on each additional partitions.
2519 """
2520 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2521
Doug Zongker05d3dea2009-06-22 11:32:31 -07002522 def IncrementalOTA_InstallEnd(self):
2523 """Called at the end of incremental OTA installation; typically
2524 this is used to install the image for the device's baseband
2525 processor."""
2526 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002527
Tao Bao9bc6bb22015-11-09 16:58:28 -08002528 def VerifyOTA_Assertions(self):
2529 return self._DoCall("VerifyOTA_Assertions")
2530
Tao Bao76def242017-11-21 09:25:31 -08002531
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002532class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002533 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002534 self.name = name
2535 self.data = data
2536 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002537 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002538 self.sha1 = sha1(data).hexdigest()
2539
2540 @classmethod
2541 def FromLocalFile(cls, name, diskname):
2542 f = open(diskname, "rb")
2543 data = f.read()
2544 f.close()
2545 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002546
2547 def WriteToTemp(self):
2548 t = tempfile.NamedTemporaryFile()
2549 t.write(self.data)
2550 t.flush()
2551 return t
2552
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002553 def WriteToDir(self, d):
2554 with open(os.path.join(d, self.name), "wb") as fp:
2555 fp.write(self.data)
2556
Geremy Condra36bd3652014-02-06 19:45:10 -08002557 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002558 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002559
Tao Bao76def242017-11-21 09:25:31 -08002560
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002561DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002562 ".gz": "imgdiff",
2563 ".zip": ["imgdiff", "-z"],
2564 ".jar": ["imgdiff", "-z"],
2565 ".apk": ["imgdiff", "-z"],
2566 ".img": "imgdiff",
2567}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002568
Tao Bao76def242017-11-21 09:25:31 -08002569
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002570class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002571 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002572 self.tf = tf
2573 self.sf = sf
2574 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002575 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002576
2577 def ComputePatch(self):
2578 """Compute the patch (as a string of data) needed to turn sf into
2579 tf. Returns the same tuple as GetPatch()."""
2580
2581 tf = self.tf
2582 sf = self.sf
2583
Doug Zongker24cd2802012-08-14 16:36:15 -07002584 if self.diff_program:
2585 diff_program = self.diff_program
2586 else:
2587 ext = os.path.splitext(tf.name)[1]
2588 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002589
2590 ttemp = tf.WriteToTemp()
2591 stemp = sf.WriteToTemp()
2592
2593 ext = os.path.splitext(tf.name)[1]
2594
2595 try:
2596 ptemp = tempfile.NamedTemporaryFile()
2597 if isinstance(diff_program, list):
2598 cmd = copy.copy(diff_program)
2599 else:
2600 cmd = [diff_program]
2601 cmd.append(stemp.name)
2602 cmd.append(ttemp.name)
2603 cmd.append(ptemp.name)
2604 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002605 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002606
Doug Zongkerf8340082014-08-05 10:39:37 -07002607 def run():
2608 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002609 if e:
2610 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002611 th = threading.Thread(target=run)
2612 th.start()
2613 th.join(timeout=300) # 5 mins
2614 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002615 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002616 p.terminate()
2617 th.join(5)
2618 if th.is_alive():
2619 p.kill()
2620 th.join()
2621
Tianjie Xua2a9f992018-01-05 15:15:54 -08002622 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002623 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002624 self.patch = None
2625 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002626 diff = ptemp.read()
2627 finally:
2628 ptemp.close()
2629 stemp.close()
2630 ttemp.close()
2631
2632 self.patch = diff
2633 return self.tf, self.sf, self.patch
2634
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002635 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002636 """Returns a tuple of (target_file, source_file, patch_data).
2637
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002638 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002639 computing the patch failed.
2640 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002641 return self.tf, self.sf, self.patch
2642
2643
2644def ComputeDifferences(diffs):
2645 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002646 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002647
2648 # Do the largest files first, to try and reduce the long-pole effect.
2649 by_size = [(i.tf.size, i) for i in diffs]
2650 by_size.sort(reverse=True)
2651 by_size = [i[1] for i in by_size]
2652
2653 lock = threading.Lock()
2654 diff_iter = iter(by_size) # accessed under lock
2655
2656 def worker():
2657 try:
2658 lock.acquire()
2659 for d in diff_iter:
2660 lock.release()
2661 start = time.time()
2662 d.ComputePatch()
2663 dur = time.time() - start
2664 lock.acquire()
2665
2666 tf, sf, patch = d.GetPatch()
2667 if sf.name == tf.name:
2668 name = tf.name
2669 else:
2670 name = "%s (%s)" % (tf.name, sf.name)
2671 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002672 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002673 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002674 logger.info(
2675 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2676 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002677 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002678 except Exception:
2679 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002680 raise
2681
2682 # start worker threads; wait for them all to finish.
2683 threads = [threading.Thread(target=worker)
2684 for i in range(OPTIONS.worker_threads)]
2685 for th in threads:
2686 th.start()
2687 while threads:
2688 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002689
2690
Dan Albert8b72aef2015-03-23 19:13:21 -07002691class BlockDifference(object):
2692 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002693 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002694 self.tgt = tgt
2695 self.src = src
2696 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002697 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002698 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002699
Tao Baodd2a5892015-03-12 12:32:37 -07002700 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002701 version = max(
2702 int(i) for i in
2703 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002704 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002705 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002706
Tianjie Xu41976c72019-07-03 13:57:01 -07002707 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2708 version=self.version,
2709 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002710 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002711 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002712 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002713 self.touched_src_ranges = b.touched_src_ranges
2714 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002715
Yifan Hong10c530d2018-12-27 17:34:18 -08002716 # On devices with dynamic partitions, for new partitions,
2717 # src is None but OPTIONS.source_info_dict is not.
2718 if OPTIONS.source_info_dict is None:
2719 is_dynamic_build = OPTIONS.info_dict.get(
2720 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002721 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002722 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002723 is_dynamic_build = OPTIONS.source_info_dict.get(
2724 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002725 is_dynamic_source = partition in shlex.split(
2726 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002727
Yifan Hongbb2658d2019-01-25 12:30:58 -08002728 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002729 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2730
Yifan Hongbb2658d2019-01-25 12:30:58 -08002731 # For dynamic partitions builds, check partition list in both source
2732 # and target build because new partitions may be added, and existing
2733 # partitions may be removed.
2734 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2735
Yifan Hong10c530d2018-12-27 17:34:18 -08002736 if is_dynamic:
2737 self.device = 'map_partition("%s")' % partition
2738 else:
2739 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002740 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2741 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002742 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002743 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2744 OPTIONS.source_info_dict)
2745 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002746
Tao Baod8d14be2016-02-04 14:26:02 -08002747 @property
2748 def required_cache(self):
2749 return self._required_cache
2750
Tao Bao76def242017-11-21 09:25:31 -08002751 def WriteScript(self, script, output_zip, progress=None,
2752 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002753 if not self.src:
2754 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002755 script.Print("Patching %s image unconditionally..." % (self.partition,))
2756 else:
2757 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002758
Dan Albert8b72aef2015-03-23 19:13:21 -07002759 if progress:
2760 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002761 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002762
2763 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002764 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002765
Tao Bao9bc6bb22015-11-09 16:58:28 -08002766 def WriteStrictVerifyScript(self, script):
2767 """Verify all the blocks in the care_map, including clobbered blocks.
2768
2769 This differs from the WriteVerifyScript() function: a) it prints different
2770 error messages; b) it doesn't allow half-way updated images to pass the
2771 verification."""
2772
2773 partition = self.partition
2774 script.Print("Verifying %s..." % (partition,))
2775 ranges = self.tgt.care_map
2776 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002777 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002778 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2779 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002780 self.device, ranges_str,
2781 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002782 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002783 script.AppendExtra("")
2784
Tao Baod522bdc2016-04-12 15:53:16 -07002785 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002786 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002787
2788 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002789 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002790 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002791
2792 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002793 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002794 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002795 ranges = self.touched_src_ranges
2796 expected_sha1 = self.touched_src_sha1
2797 else:
2798 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2799 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002800
2801 # No blocks to be checked, skipping.
2802 if not ranges:
2803 return
2804
Tao Bao5ece99d2015-05-12 11:42:31 -07002805 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002806 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002807 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002808 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2809 '"%s.patch.dat")) then' % (
2810 self.device, ranges_str, expected_sha1,
2811 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002812 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002813 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002814
Tianjie Xufc3422a2015-12-15 11:53:59 -08002815 if self.version >= 4:
2816
2817 # Bug: 21124327
2818 # When generating incrementals for the system and vendor partitions in
2819 # version 4 or newer, explicitly check the first block (which contains
2820 # the superblock) of the partition to see if it's what we expect. If
2821 # this check fails, give an explicit log message about the partition
2822 # having been remounted R/W (the most likely explanation).
2823 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002824 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002825
2826 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002827 if partition == "system":
2828 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2829 else:
2830 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002831 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002832 'ifelse (block_image_recover({device}, "{ranges}") && '
2833 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002834 'package_extract_file("{partition}.transfer.list"), '
2835 '"{partition}.new.dat", "{partition}.patch.dat"), '
2836 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002837 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002838 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002839 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002840
Tao Baodd2a5892015-03-12 12:32:37 -07002841 # Abort the OTA update. Note that the incremental OTA cannot be applied
2842 # even if it may match the checksum of the target partition.
2843 # a) If version < 3, operations like move and erase will make changes
2844 # unconditionally and damage the partition.
2845 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002846 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002847 if partition == "system":
2848 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2849 else:
2850 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2851 script.AppendExtra((
2852 'abort("E%d: %s partition has unexpected contents");\n'
2853 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002854
Yifan Hong10c530d2018-12-27 17:34:18 -08002855 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002856 partition = self.partition
2857 script.Print('Verifying the updated %s image...' % (partition,))
2858 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2859 ranges = self.tgt.care_map
2860 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002861 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002862 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002863 self.device, ranges_str,
2864 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002865
2866 # Bug: 20881595
2867 # Verify that extended blocks are really zeroed out.
2868 if self.tgt.extended:
2869 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002870 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002871 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002872 self.device, ranges_str,
2873 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002874 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002875 if partition == "system":
2876 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2877 else:
2878 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002879 script.AppendExtra(
2880 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002881 ' abort("E%d: %s partition has unexpected non-zero contents after '
2882 'OTA update");\n'
2883 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002884 else:
2885 script.Print('Verified the updated %s image.' % (partition,))
2886
Tianjie Xu209db462016-05-24 17:34:52 -07002887 if partition == "system":
2888 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2889 else:
2890 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2891
Tao Bao5fcaaef2015-06-01 13:40:49 -07002892 script.AppendExtra(
2893 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002894 ' abort("E%d: %s partition has unexpected contents after OTA '
2895 'update");\n'
2896 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002897
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002898 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002899 ZipWrite(output_zip,
2900 '{}.transfer.list'.format(self.path),
2901 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002902
Tao Bao76def242017-11-21 09:25:31 -08002903 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2904 # its size. Quailty 9 almost triples the compression time but doesn't
2905 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002906 # zip | brotli(quality 6) | brotli(quality 9)
2907 # compressed_size: 942M | 869M (~8% reduced) | 854M
2908 # compression_time: 75s | 265s | 719s
2909 # decompression_time: 15s | 25s | 25s
2910
2911 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002912 brotli_cmd = ['brotli', '--quality=6',
2913 '--output={}.new.dat.br'.format(self.path),
2914 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002915 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002916 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002917
2918 new_data_name = '{}.new.dat.br'.format(self.partition)
2919 ZipWrite(output_zip,
2920 '{}.new.dat.br'.format(self.path),
2921 new_data_name,
2922 compress_type=zipfile.ZIP_STORED)
2923 else:
2924 new_data_name = '{}.new.dat'.format(self.partition)
2925 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2926
Dan Albert8e0178d2015-01-27 15:53:15 -08002927 ZipWrite(output_zip,
2928 '{}.patch.dat'.format(self.path),
2929 '{}.patch.dat'.format(self.partition),
2930 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002931
Tianjie Xu209db462016-05-24 17:34:52 -07002932 if self.partition == "system":
2933 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2934 else:
2935 code = ErrorCode.VENDOR_UPDATE_FAILURE
2936
Yifan Hong10c530d2018-12-27 17:34:18 -08002937 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002938 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002939 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002940 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002941 device=self.device, partition=self.partition,
2942 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002943 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002944
Kelvin Zhang0876c412020-06-23 15:06:58 -04002945 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002946 data = source.ReadRangeSet(ranges)
2947 ctx = sha1()
2948
2949 for p in data:
2950 ctx.update(p)
2951
2952 return ctx.hexdigest()
2953
Kelvin Zhang0876c412020-06-23 15:06:58 -04002954 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07002955 """Return the hash value for all zero blocks."""
2956 zero_block = '\x00' * 4096
2957 ctx = sha1()
2958 for _ in range(num_blocks):
2959 ctx.update(zero_block)
2960
2961 return ctx.hexdigest()
2962
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002963
Tianjie Xu41976c72019-07-03 13:57:01 -07002964# Expose these two classes to support vendor-specific scripts
2965DataImage = images.DataImage
2966EmptyImage = images.EmptyImage
2967
Tao Bao76def242017-11-21 09:25:31 -08002968
Doug Zongker96a57e72010-09-26 14:57:41 -07002969# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002970PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002971 "ext4": "EMMC",
2972 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002973 "f2fs": "EMMC",
2974 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002975}
Doug Zongker96a57e72010-09-26 14:57:41 -07002976
Kelvin Zhang0876c412020-06-23 15:06:58 -04002977
Yifan Hongbdb32012020-05-07 12:38:53 -07002978def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2979 """
2980 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2981 backwards compatibility. It aborts if the fstab entry has slotselect option
2982 (unless check_no_slot is explicitly set to False).
2983 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002984 fstab = info["fstab"]
2985 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07002986 if check_no_slot:
2987 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04002988 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002989 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2990 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002991 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002992
2993
Yifan Hongbdb32012020-05-07 12:38:53 -07002994def GetTypeAndDeviceExpr(mount_point, info):
2995 """
2996 Return the filesystem of the partition, and an edify expression that evaluates
2997 to the device at runtime.
2998 """
2999 fstab = info["fstab"]
3000 if fstab:
3001 p = fstab[mount_point]
3002 device_expr = '"%s"' % fstab[mount_point].device
3003 if p.slotselect:
3004 device_expr = 'add_slot_suffix(%s)' % device_expr
3005 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003006 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003007
3008
3009def GetEntryForDevice(fstab, device):
3010 """
3011 Returns:
3012 The first entry in fstab whose device is the given value.
3013 """
3014 if not fstab:
3015 return None
3016 for mount_point in fstab:
3017 if fstab[mount_point].device == device:
3018 return fstab[mount_point]
3019 return None
3020
Kelvin Zhang0876c412020-06-23 15:06:58 -04003021
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003022def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003023 """Parses and converts a PEM-encoded certificate into DER-encoded.
3024
3025 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3026
3027 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003028 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003029 """
3030 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003031 save = False
3032 for line in data.split("\n"):
3033 if "--END CERTIFICATE--" in line:
3034 break
3035 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003036 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003037 if "--BEGIN CERTIFICATE--" in line:
3038 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003039 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003040 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003041
Tao Bao04e1f012018-02-04 12:13:35 -08003042
3043def ExtractPublicKey(cert):
3044 """Extracts the public key (PEM-encoded) from the given certificate file.
3045
3046 Args:
3047 cert: The certificate filename.
3048
3049 Returns:
3050 The public key string.
3051
3052 Raises:
3053 AssertionError: On non-zero return from 'openssl'.
3054 """
3055 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3056 # While openssl 1.1 writes the key into the given filename followed by '-out',
3057 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3058 # stdout instead.
3059 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3060 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3061 pubkey, stderrdata = proc.communicate()
3062 assert proc.returncode == 0, \
3063 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3064 return pubkey
3065
3066
Tao Bao1ac886e2019-06-26 11:58:22 -07003067def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003068 """Extracts the AVB public key from the given public or private key.
3069
3070 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003071 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003072 key: The input key file, which should be PEM-encoded public or private key.
3073
3074 Returns:
3075 The path to the extracted AVB public key file.
3076 """
3077 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3078 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003079 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003080 return output
3081
3082
Doug Zongker412c02f2014-02-13 10:58:24 -08003083def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3084 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003085 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003086
Tao Bao6d5d6232018-03-09 17:04:42 -08003087 Most of the space in the boot and recovery images is just the kernel, which is
3088 identical for the two, so the resulting patch should be efficient. Add it to
3089 the output zip, along with a shell script that is run from init.rc on first
3090 boot to actually do the patching and install the new recovery image.
3091
3092 Args:
3093 input_dir: The top-level input directory of the target-files.zip.
3094 output_sink: The callback function that writes the result.
3095 recovery_img: File object for the recovery image.
3096 boot_img: File objects for the boot image.
3097 info_dict: A dict returned by common.LoadInfoDict() on the input
3098 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003099 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003100 if info_dict is None:
3101 info_dict = OPTIONS.info_dict
3102
Tao Bao6d5d6232018-03-09 17:04:42 -08003103 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003104 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3105
3106 if board_uses_vendorimage:
3107 # In this case, the output sink is rooted at VENDOR
3108 recovery_img_path = "etc/recovery.img"
3109 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3110 sh_dir = "bin"
3111 else:
3112 # In this case the output sink is rooted at SYSTEM
3113 recovery_img_path = "vendor/etc/recovery.img"
3114 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3115 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003116
Tao Baof2cffbd2015-07-22 12:33:18 -07003117 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003118 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003119
3120 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003121 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003122 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003123 # With system-root-image, boot and recovery images will have mismatching
3124 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3125 # to handle such a case.
3126 if system_root_image:
3127 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003128 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003129 assert not os.path.exists(path)
3130 else:
3131 diff_program = ["imgdiff"]
3132 if os.path.exists(path):
3133 diff_program.append("-b")
3134 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003135 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003136 else:
3137 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003138
3139 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3140 _, _, patch = d.ComputePatch()
3141 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003142
Dan Albertebb19aa2015-03-27 19:11:53 -07003143 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003144 # The following GetTypeAndDevice()s need to use the path in the target
3145 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003146 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3147 check_no_slot=False)
3148 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3149 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003150 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003151 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003152
Tao Baof2cffbd2015-07-22 12:33:18 -07003153 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003154
3155 # Note that we use /vendor to refer to the recovery resources. This will
3156 # work for a separate vendor partition mounted at /vendor or a
3157 # /system/vendor subdirectory on the system partition, for which init will
3158 # create a symlink from /vendor to /system/vendor.
3159
3160 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003161if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3162 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003163 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003164 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3165 log -t recovery "Installing new recovery image: succeeded" || \\
3166 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003167else
3168 log -t recovery "Recovery image already installed"
3169fi
3170""" % {'type': recovery_type,
3171 'device': recovery_device,
3172 'sha1': recovery_img.sha1,
3173 'size': recovery_img.size}
3174 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003175 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003176if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3177 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003178 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003179 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3180 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3181 log -t recovery "Installing new recovery image: succeeded" || \\
3182 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003183else
3184 log -t recovery "Recovery image already installed"
3185fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003186""" % {'boot_size': boot_img.size,
3187 'boot_sha1': boot_img.sha1,
3188 'recovery_size': recovery_img.size,
3189 'recovery_sha1': recovery_img.sha1,
3190 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003191 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003192 'recovery_type': recovery_type,
3193 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003194 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003195
Bill Peckhame868aec2019-09-17 17:06:47 -07003196 # The install script location moved from /system/etc to /system/bin in the L
3197 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3198 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003199
Tao Bao32fcdab2018-10-12 10:30:39 -07003200 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003201
Tao Baoda30cfa2017-12-01 16:19:46 -08003202 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003203
3204
3205class DynamicPartitionUpdate(object):
3206 def __init__(self, src_group=None, tgt_group=None, progress=None,
3207 block_difference=None):
3208 self.src_group = src_group
3209 self.tgt_group = tgt_group
3210 self.progress = progress
3211 self.block_difference = block_difference
3212
3213 @property
3214 def src_size(self):
3215 if not self.block_difference:
3216 return 0
3217 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3218
3219 @property
3220 def tgt_size(self):
3221 if not self.block_difference:
3222 return 0
3223 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3224
3225 @staticmethod
3226 def _GetSparseImageSize(img):
3227 if not img:
3228 return 0
3229 return img.blocksize * img.total_blocks
3230
3231
3232class DynamicGroupUpdate(object):
3233 def __init__(self, src_size=None, tgt_size=None):
3234 # None: group does not exist. 0: no size limits.
3235 self.src_size = src_size
3236 self.tgt_size = tgt_size
3237
3238
3239class DynamicPartitionsDifference(object):
3240 def __init__(self, info_dict, block_diffs, progress_dict=None,
3241 source_info_dict=None):
3242 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003243 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003244
3245 self._remove_all_before_apply = False
3246 if source_info_dict is None:
3247 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003248 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003249
Tao Baof1113e92019-06-18 12:10:14 -07003250 block_diff_dict = collections.OrderedDict(
3251 [(e.partition, e) for e in block_diffs])
3252
Yifan Hong10c530d2018-12-27 17:34:18 -08003253 assert len(block_diff_dict) == len(block_diffs), \
3254 "Duplicated BlockDifference object for {}".format(
3255 [partition for partition, count in
3256 collections.Counter(e.partition for e in block_diffs).items()
3257 if count > 1])
3258
Yifan Hong79997e52019-01-23 16:56:19 -08003259 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003260
3261 for p, block_diff in block_diff_dict.items():
3262 self._partition_updates[p] = DynamicPartitionUpdate()
3263 self._partition_updates[p].block_difference = block_diff
3264
3265 for p, progress in progress_dict.items():
3266 if p in self._partition_updates:
3267 self._partition_updates[p].progress = progress
3268
3269 tgt_groups = shlex.split(info_dict.get(
3270 "super_partition_groups", "").strip())
3271 src_groups = shlex.split(source_info_dict.get(
3272 "super_partition_groups", "").strip())
3273
3274 for g in tgt_groups:
3275 for p in shlex.split(info_dict.get(
3276 "super_%s_partition_list" % g, "").strip()):
3277 assert p in self._partition_updates, \
3278 "{} is in target super_{}_partition_list but no BlockDifference " \
3279 "object is provided.".format(p, g)
3280 self._partition_updates[p].tgt_group = g
3281
3282 for g in src_groups:
3283 for p in shlex.split(source_info_dict.get(
3284 "super_%s_partition_list" % g, "").strip()):
3285 assert p in self._partition_updates, \
3286 "{} is in source super_{}_partition_list but no BlockDifference " \
3287 "object is provided.".format(p, g)
3288 self._partition_updates[p].src_group = g
3289
Yifan Hong45433e42019-01-18 13:55:25 -08003290 target_dynamic_partitions = set(shlex.split(info_dict.get(
3291 "dynamic_partition_list", "").strip()))
3292 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3293 if u.tgt_size)
3294 assert block_diffs_with_target == target_dynamic_partitions, \
3295 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3296 list(target_dynamic_partitions), list(block_diffs_with_target))
3297
3298 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3299 "dynamic_partition_list", "").strip()))
3300 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3301 if u.src_size)
3302 assert block_diffs_with_source == source_dynamic_partitions, \
3303 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3304 list(source_dynamic_partitions), list(block_diffs_with_source))
3305
Yifan Hong10c530d2018-12-27 17:34:18 -08003306 if self._partition_updates:
3307 logger.info("Updating dynamic partitions %s",
3308 self._partition_updates.keys())
3309
Yifan Hong79997e52019-01-23 16:56:19 -08003310 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003311
3312 for g in tgt_groups:
3313 self._group_updates[g] = DynamicGroupUpdate()
3314 self._group_updates[g].tgt_size = int(info_dict.get(
3315 "super_%s_group_size" % g, "0").strip())
3316
3317 for g in src_groups:
3318 if g not in self._group_updates:
3319 self._group_updates[g] = DynamicGroupUpdate()
3320 self._group_updates[g].src_size = int(source_info_dict.get(
3321 "super_%s_group_size" % g, "0").strip())
3322
3323 self._Compute()
3324
3325 def WriteScript(self, script, output_zip, write_verify_script=False):
3326 script.Comment('--- Start patching dynamic partitions ---')
3327 for p, u in self._partition_updates.items():
3328 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3329 script.Comment('Patch partition %s' % p)
3330 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3331 write_verify_script=False)
3332
3333 op_list_path = MakeTempFile()
3334 with open(op_list_path, 'w') as f:
3335 for line in self._op_list:
3336 f.write('{}\n'.format(line))
3337
3338 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3339
3340 script.Comment('Update dynamic partition metadata')
3341 script.AppendExtra('assert(update_dynamic_partitions('
3342 'package_extract_file("dynamic_partitions_op_list")));')
3343
3344 if write_verify_script:
3345 for p, u in self._partition_updates.items():
3346 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3347 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003348 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003349
3350 for p, u in self._partition_updates.items():
3351 if u.tgt_size and u.src_size <= u.tgt_size:
3352 script.Comment('Patch partition %s' % p)
3353 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3354 write_verify_script=write_verify_script)
3355 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003356 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003357
3358 script.Comment('--- End patching dynamic partitions ---')
3359
3360 def _Compute(self):
3361 self._op_list = list()
3362
3363 def append(line):
3364 self._op_list.append(line)
3365
3366 def comment(line):
3367 self._op_list.append("# %s" % line)
3368
3369 if self._remove_all_before_apply:
3370 comment('Remove all existing dynamic partitions and groups before '
3371 'applying full OTA')
3372 append('remove_all_groups')
3373
3374 for p, u in self._partition_updates.items():
3375 if u.src_group and not u.tgt_group:
3376 append('remove %s' % p)
3377
3378 for p, u in self._partition_updates.items():
3379 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3380 comment('Move partition %s from %s to default' % (p, u.src_group))
3381 append('move %s default' % p)
3382
3383 for p, u in self._partition_updates.items():
3384 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3385 comment('Shrink partition %s from %d to %d' %
3386 (p, u.src_size, u.tgt_size))
3387 append('resize %s %s' % (p, u.tgt_size))
3388
3389 for g, u in self._group_updates.items():
3390 if u.src_size is not None and u.tgt_size is None:
3391 append('remove_group %s' % g)
3392 if (u.src_size is not None and u.tgt_size is not None and
3393 u.src_size > u.tgt_size):
3394 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3395 append('resize_group %s %d' % (g, u.tgt_size))
3396
3397 for g, u in self._group_updates.items():
3398 if u.src_size is None and u.tgt_size is not None:
3399 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3400 append('add_group %s %d' % (g, u.tgt_size))
3401 if (u.src_size is not None and u.tgt_size is not None and
3402 u.src_size < u.tgt_size):
3403 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3404 append('resize_group %s %d' % (g, u.tgt_size))
3405
3406 for p, u in self._partition_updates.items():
3407 if u.tgt_group and not u.src_group:
3408 comment('Add partition %s to group %s' % (p, u.tgt_group))
3409 append('add %s %s' % (p, u.tgt_group))
3410
3411 for p, u in self._partition_updates.items():
3412 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003413 comment('Grow partition %s from %d to %d' %
3414 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003415 append('resize %s %d' % (p, u.tgt_size))
3416
3417 for p, u in self._partition_updates.items():
3418 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3419 comment('Move partition %s from default to %s' %
3420 (p, u.tgt_group))
3421 append('move %s %s' % (p, u.tgt_group))