blob: 71ae21fa8aba915cfeb3c18333e3454b433ef9d7 [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
Doug Zongker8ce7c252009-05-22 13:34:54 -070020import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070021import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070022import getopt
23import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010024import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070025import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070026import json
27import logging
28import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070029import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080030import platform
Doug Zongkereef39442009-04-02 12:14:19 -070031import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070032import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070033import shutil
34import subprocess
35import sys
36import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070037import threading
38import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070039import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080040from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070041
Tianjie Xu41976c72019-07-03 13:57:01 -070042import images
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070044from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070045
Tao Bao32fcdab2018-10-12 10:30:39 -070046logger = logging.getLogger(__name__)
47
Tao Bao986ee862018-10-04 15:46:16 -070048
Dan Albert8b72aef2015-03-23 19:13:21 -070049class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070052 # Set up search path, in order to find framework/ and lib64/. At the time of
53 # running this function, user-supplied search path (`--path`) hasn't been
54 # available. So the value set here is the default, which might be overridden
55 # by commandline flag later.
56 exec_path = sys.argv[0]
57 if exec_path.endswith('.py'):
58 script_name = os.path.basename(exec_path)
59 # logger hasn't been initialized yet at this point. Use print to output
60 # warnings.
61 print(
62 'Warning: releasetools script should be invoked as hermetic Python '
63 'executable -- build and run `{}` directly.'.format(script_name[:-3]),
64 file=sys.stderr)
Robin Lee34ea7392020-01-02 20:21:18 +010065 self.search_path = os.path.realpath(os.path.join(os.path.dirname(exec_path), '..'))
Pavel Salomatov32676552019-03-06 20:00:45 +030066
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080068 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.extra_signapk_args = []
70 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080071 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080072 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070073 self.public_key_suffix = ".x509.pem"
74 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070075 # use otatools built boot_signer by default
76 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070077 self.boot_signer_args = []
78 self.verity_signer_path = None
79 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070080 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080081 self.aftl_server = None
82 self.aftl_key_path = None
83 self.aftl_manufacturer_key_path = None
84 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070085 self.verbose = False
86 self.tempfiles = []
87 self.device_specific = None
88 self.extras = {}
89 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070090 self.source_info_dict = None
91 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070092 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070093 # Stash size cannot exceed cache_size * threshold.
94 self.cache_size = None
95 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070096 self.logfile = None
Dan Albert8b72aef2015-03-23 19:13:21 -070097
98
99OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700100
Tao Bao71197512018-10-11 14:08:45 -0700101# The block size that's used across the releasetools scripts.
102BLOCK_SIZE = 4096
103
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800104# Values for "certificate" in apkcerts that mean special things.
105SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
106
Tao Bao5cc0abb2019-03-21 10:18:05 -0700107# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
108# that system_other is not in the list because we don't want to include its
109# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900110AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Steve Mucklee1b10862019-07-10 10:49:37 -0700111 'system_ext', 'vendor', 'vendor_boot')
Tao Bao9dd909e2017-11-14 11:27:32 -0800112
Tao Bao08c190f2019-06-03 23:07:58 -0700113# Chained VBMeta partitions.
114AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
115
Tianjie Xu861f4132018-09-12 11:49:33 -0700116# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900117PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700118
119
Tianjie Xu209db462016-05-24 17:34:52 -0700120class ErrorCode(object):
121 """Define error_codes for failures that happen during the actual
122 update package installation.
123
124 Error codes 0-999 are reserved for failures before the package
125 installation (i.e. low battery, package verification failure).
126 Detailed code in 'bootable/recovery/error_code.h' """
127
128 SYSTEM_VERIFICATION_FAILURE = 1000
129 SYSTEM_UPDATE_FAILURE = 1001
130 SYSTEM_UNEXPECTED_CONTENTS = 1002
131 SYSTEM_NONZERO_CONTENTS = 1003
132 SYSTEM_RECOVER_FAILURE = 1004
133 VENDOR_VERIFICATION_FAILURE = 2000
134 VENDOR_UPDATE_FAILURE = 2001
135 VENDOR_UNEXPECTED_CONTENTS = 2002
136 VENDOR_NONZERO_CONTENTS = 2003
137 VENDOR_RECOVER_FAILURE = 2004
138 OEM_PROP_MISMATCH = 3000
139 FINGERPRINT_MISMATCH = 3001
140 THUMBPRINT_MISMATCH = 3002
141 OLDER_BUILD = 3003
142 DEVICE_MISMATCH = 3004
143 BAD_PATCH_FILE = 3005
144 INSUFFICIENT_CACHE_SPACE = 3006
145 TUNE_PARTITION_FAILURE = 3007
146 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800147
Tao Bao80921982018-03-21 21:02:19 -0700148
Dan Albert8b72aef2015-03-23 19:13:21 -0700149class ExternalError(RuntimeError):
150 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700151
152
Tao Bao32fcdab2018-10-12 10:30:39 -0700153def InitLogging():
154 DEFAULT_LOGGING_CONFIG = {
155 'version': 1,
156 'disable_existing_loggers': False,
157 'formatters': {
158 'standard': {
159 'format':
160 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
161 'datefmt': '%Y-%m-%d %H:%M:%S',
162 },
163 },
164 'handlers': {
165 'default': {
166 'class': 'logging.StreamHandler',
167 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700168 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700169 },
170 },
171 'loggers': {
172 '': {
173 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700174 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700175 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700176 }
177 }
178 }
179 env_config = os.getenv('LOGGING_CONFIG')
180 if env_config:
181 with open(env_config) as f:
182 config = json.load(f)
183 else:
184 config = DEFAULT_LOGGING_CONFIG
185
186 # Increase the logging level for verbose mode.
187 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700188 config = copy.deepcopy(config)
189 config['handlers']['default']['level'] = 'INFO'
190
191 if OPTIONS.logfile:
192 config = copy.deepcopy(config)
193 config['handlers']['logfile'] = {
194 'class': 'logging.FileHandler',
195 'formatter': 'standard',
196 'level': 'INFO',
197 'mode': 'w',
198 'filename': OPTIONS.logfile,
199 }
200 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700201
202 logging.config.dictConfig(config)
203
204
Tao Bao39451582017-05-04 11:10:47 -0700205def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700206 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700207
Tao Bao73dd4f42018-10-04 16:25:33 -0700208 Args:
209 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700210 verbose: Whether the commands should be shown. Default to the global
211 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700212 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
213 stdin, etc. stdout and stderr will default to subprocess.PIPE and
214 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800215 universal_newlines will default to True, as most of the users in
216 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700217
218 Returns:
219 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700220 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700221 if 'stdout' not in kwargs and 'stderr' not in kwargs:
222 kwargs['stdout'] = subprocess.PIPE
223 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800224 if 'universal_newlines' not in kwargs:
225 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700226 # Don't log any if caller explicitly says so.
227 if verbose != False:
228 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700229 return subprocess.Popen(args, **kwargs)
230
231
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800232def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800233 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800234
235 Args:
236 args: The command represented as a list of strings.
237 verbose: Whether the commands should be shown. Default to the global
238 verbosity if unspecified.
239 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
240 stdin, etc. stdout and stderr will default to subprocess.PIPE and
241 subprocess.STDOUT respectively unless caller specifies any of them.
242
Bill Peckham889b0c62019-02-21 18:53:37 -0800243 Raises:
244 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800245 """
246 proc = Run(args, verbose=verbose, **kwargs)
247 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800248
249 if proc.returncode != 0:
250 raise ExternalError(
251 "Failed to run command '{}' (exit code {})".format(
252 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800253
254
Tao Bao986ee862018-10-04 15:46:16 -0700255def RunAndCheckOutput(args, verbose=None, **kwargs):
256 """Runs the given command and returns the output.
257
258 Args:
259 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700260 verbose: Whether the commands should be shown. Default to the global
261 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700262 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
263 stdin, etc. stdout and stderr will default to subprocess.PIPE and
264 subprocess.STDOUT respectively unless caller specifies any of them.
265
266 Returns:
267 The output string.
268
269 Raises:
270 ExternalError: On non-zero exit from the command.
271 """
Tao Bao986ee862018-10-04 15:46:16 -0700272 proc = Run(args, verbose=verbose, **kwargs)
273 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800274 if output is None:
275 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700276 # Don't log any if caller explicitly says so.
277 if verbose != False:
278 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700279 if proc.returncode != 0:
280 raise ExternalError(
281 "Failed to run command '{}' (exit code {}):\n{}".format(
282 args, proc.returncode, output))
283 return output
284
285
Tao Baoc765cca2018-01-31 17:32:40 -0800286def RoundUpTo4K(value):
287 rounded_up = value + 4095
288 return rounded_up - (rounded_up % 4096)
289
290
Ying Wang7e6d4e42010-12-13 16:25:36 -0800291def CloseInheritedPipes():
292 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
293 before doing other work."""
294 if platform.system() != "Darwin":
295 return
296 for d in range(3, 1025):
297 try:
298 stat = os.fstat(d)
299 if stat is not None:
300 pipebit = stat[0] & 0x1000
301 if pipebit != 0:
302 os.close(d)
303 except OSError:
304 pass
305
306
Tao Bao1c320f82019-10-04 23:25:12 -0700307class BuildInfo(object):
308 """A class that holds the information for a given build.
309
310 This class wraps up the property querying for a given source or target build.
311 It abstracts away the logic of handling OEM-specific properties, and caches
312 the commonly used properties such as fingerprint.
313
314 There are two types of info dicts: a) build-time info dict, which is generated
315 at build time (i.e. included in a target_files zip); b) OEM info dict that is
316 specified at package generation time (via command line argument
317 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
318 having "oem_fingerprint_properties" in build-time info dict), all the queries
319 would be answered based on build-time info dict only. Otherwise if using
320 OEM-specific properties, some of them will be calculated from two info dicts.
321
322 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800323 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700324
325 Attributes:
326 info_dict: The build-time info dict.
327 is_ab: Whether it's a build that uses A/B OTA.
328 oem_dicts: A list of OEM dicts.
329 oem_props: A list of OEM properties that should be read from OEM dicts; None
330 if the build doesn't use any OEM-specific property.
331 fingerprint: The fingerprint of the build, which would be calculated based
332 on OEM properties if applicable.
333 device: The device name, which could come from OEM dicts if applicable.
334 """
335
336 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
337 "ro.product.manufacturer", "ro.product.model",
338 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700339 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
340 "product", "odm", "vendor", "system_ext", "system"]
341 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
342 "product", "product_services", "odm", "vendor", "system"]
343 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700344
Tao Bao3ed35d32019-10-07 20:48:48 -0700345 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700346 """Initializes a BuildInfo instance with the given dicts.
347
348 Note that it only wraps up the given dicts, without making copies.
349
350 Arguments:
351 info_dict: The build-time info dict.
352 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
353 that it always uses the first dict to calculate the fingerprint or the
354 device name. The rest would be used for asserting OEM properties only
355 (e.g. one package can be installed on one of these devices).
356
357 Raises:
358 ValueError: On invalid inputs.
359 """
360 self.info_dict = info_dict
361 self.oem_dicts = oem_dicts
362
363 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700364
Hongguang Chend7c160f2020-05-03 21:24:26 -0700365 # Skip _oem_props if oem_dicts is None to use BuildInfo in
366 # sign_target_files_apks
367 if self.oem_dicts:
368 self._oem_props = info_dict.get("oem_fingerprint_properties")
369 else:
370 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700371
Daniel Normand5fe8622020-01-08 17:01:11 -0800372 def check_fingerprint(fingerprint):
373 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
374 raise ValueError(
375 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
376 "3.2.2. Build Parameters.".format(fingerprint))
377
378
379 self._partition_fingerprints = {}
380 for partition in PARTITIONS_WITH_CARE_MAP:
381 try:
382 fingerprint = self.CalculatePartitionFingerprint(partition)
383 check_fingerprint(fingerprint)
384 self._partition_fingerprints[partition] = fingerprint
385 except ExternalError:
386 continue
387 if "system" in self._partition_fingerprints:
388 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
389 # need a fingerprint when creating the image.
390 self._partition_fingerprints[
391 "system_other"] = self._partition_fingerprints["system"]
392
Tao Bao1c320f82019-10-04 23:25:12 -0700393 # These two should be computed only after setting self._oem_props.
394 self._device = self.GetOemProperty("ro.product.device")
395 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800396 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700397
398 @property
399 def is_ab(self):
400 return self._is_ab
401
402 @property
403 def device(self):
404 return self._device
405
406 @property
407 def fingerprint(self):
408 return self._fingerprint
409
410 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700411 def oem_props(self):
412 return self._oem_props
413
414 def __getitem__(self, key):
415 return self.info_dict[key]
416
417 def __setitem__(self, key, value):
418 self.info_dict[key] = value
419
420 def get(self, key, default=None):
421 return self.info_dict.get(key, default)
422
423 def items(self):
424 return self.info_dict.items()
425
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000426 def _GetRawBuildProp(self, prop, partition):
427 prop_file = '{}.build.prop'.format(
428 partition) if partition else 'build.prop'
429 partition_props = self.info_dict.get(prop_file)
430 if not partition_props:
431 return None
432 return partition_props.GetProp(prop)
433
Daniel Normand5fe8622020-01-08 17:01:11 -0800434 def GetPartitionBuildProp(self, prop, partition):
435 """Returns the inquired build property for the provided partition."""
436 # If provided a partition for this property, only look within that
437 # partition's build.prop.
438 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
439 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
440 else:
441 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000442
443 prop_val = self._GetRawBuildProp(prop, partition)
444 if prop_val is not None:
445 return prop_val
446 raise ExternalError("couldn't find %s in %s.build.prop" %
447 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800448
Tao Bao1c320f82019-10-04 23:25:12 -0700449 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800450 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700451 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
452 return self._ResolveRoProductBuildProp(prop)
453
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000454 prop_val = self._GetRawBuildProp(prop, None)
455 if prop_val is not None:
456 return prop_val
457
458 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700459
460 def _ResolveRoProductBuildProp(self, prop):
461 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000462 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700463 if prop_val:
464 return prop_val
465
Steven Laver8e2086e2020-04-27 16:26:31 -0700466 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000467 source_order_val = self._GetRawBuildProp(
468 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700469 if source_order_val:
470 source_order = source_order_val.split(",")
471 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700472 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700473
474 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700475 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700476 raise ExternalError(
477 "Invalid ro.product.property_source_order '{}'".format(source_order))
478
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000479 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700480 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000481 "ro.product", "ro.product.{}".format(source_partition), 1)
482 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700483 if prop_val:
484 return prop_val
485
486 raise ExternalError("couldn't resolve {}".format(prop))
487
Steven Laver8e2086e2020-04-27 16:26:31 -0700488 def _GetRoProductPropsDefaultSourceOrder(self):
489 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
490 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000491 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700492 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000493 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700494 if android_version == "10":
495 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
496 # NOTE: float() conversion of android_version will have rounding error.
497 # We are checking for "9" or less, and using "< 10" is well outside of
498 # possible floating point rounding.
499 try:
500 android_version_val = float(android_version)
501 except ValueError:
502 android_version_val = 0
503 if android_version_val < 10:
504 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
505 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
506
Tao Bao1c320f82019-10-04 23:25:12 -0700507 def GetOemProperty(self, key):
508 if self.oem_props is not None and key in self.oem_props:
509 return self.oem_dicts[0][key]
510 return self.GetBuildProp(key)
511
Daniel Normand5fe8622020-01-08 17:01:11 -0800512 def GetPartitionFingerprint(self, partition):
513 return self._partition_fingerprints.get(partition, None)
514
515 def CalculatePartitionFingerprint(self, partition):
516 try:
517 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
518 except ExternalError:
519 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
520 self.GetPartitionBuildProp("ro.product.brand", partition),
521 self.GetPartitionBuildProp("ro.product.name", partition),
522 self.GetPartitionBuildProp("ro.product.device", partition),
523 self.GetPartitionBuildProp("ro.build.version.release", partition),
524 self.GetPartitionBuildProp("ro.build.id", partition),
525 self.GetPartitionBuildProp("ro.build.version.incremental", partition),
526 self.GetPartitionBuildProp("ro.build.type", partition),
527 self.GetPartitionBuildProp("ro.build.tags", partition))
528
Tao Bao1c320f82019-10-04 23:25:12 -0700529 def CalculateFingerprint(self):
530 if self.oem_props is None:
531 try:
532 return self.GetBuildProp("ro.build.fingerprint")
533 except ExternalError:
534 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
535 self.GetBuildProp("ro.product.brand"),
536 self.GetBuildProp("ro.product.name"),
537 self.GetBuildProp("ro.product.device"),
538 self.GetBuildProp("ro.build.version.release"),
539 self.GetBuildProp("ro.build.id"),
540 self.GetBuildProp("ro.build.version.incremental"),
541 self.GetBuildProp("ro.build.type"),
542 self.GetBuildProp("ro.build.tags"))
543 return "%s/%s/%s:%s" % (
544 self.GetOemProperty("ro.product.brand"),
545 self.GetOemProperty("ro.product.name"),
546 self.GetOemProperty("ro.product.device"),
547 self.GetBuildProp("ro.build.thumbprint"))
548
549 def WriteMountOemScript(self, script):
550 assert self.oem_props is not None
551 recovery_mount_options = self.info_dict.get("recovery_mount_options")
552 script.Mount("/oem", recovery_mount_options)
553
554 def WriteDeviceAssertions(self, script, oem_no_mount):
555 # Read the property directly if not using OEM properties.
556 if not self.oem_props:
557 script.AssertDevice(self.device)
558 return
559
560 # Otherwise assert OEM properties.
561 if not self.oem_dicts:
562 raise ExternalError(
563 "No OEM file provided to answer expected assertions")
564
565 for prop in self.oem_props.split():
566 values = []
567 for oem_dict in self.oem_dicts:
568 if prop in oem_dict:
569 values.append(oem_dict[prop])
570 if not values:
571 raise ExternalError(
572 "The OEM file is missing the property %s" % (prop,))
573 script.AssertOemProperty(prop, values, oem_no_mount)
574
575
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000576def ReadFromInputFile(input_file, fn):
577 """Reads the contents of fn from input zipfile or directory."""
578 if isinstance(input_file, zipfile.ZipFile):
579 return input_file.read(fn).decode()
580 else:
581 path = os.path.join(input_file, *fn.split("/"))
582 try:
583 with open(path) as f:
584 return f.read()
585 except IOError as e:
586 if e.errno == errno.ENOENT:
587 raise KeyError(fn)
588
589
Tao Bao410ad8b2018-08-24 12:08:38 -0700590def LoadInfoDict(input_file, repacking=False):
591 """Loads the key/value pairs from the given input target_files.
592
593 It reads `META/misc_info.txt` file in the target_files input, does sanity
594 checks and returns the parsed key/value pairs for to the given build. It's
595 usually called early when working on input target_files files, e.g. when
596 generating OTAs, or signing builds. Note that the function may be called
597 against an old target_files file (i.e. from past dessert releases). So the
598 property parsing needs to be backward compatible.
599
600 In a `META/misc_info.txt`, a few properties are stored as links to the files
601 in the PRODUCT_OUT directory. It works fine with the build system. However,
602 they are no longer available when (re)generating images from target_files zip.
603 When `repacking` is True, redirect these properties to the actual files in the
604 unzipped directory.
605
606 Args:
607 input_file: The input target_files file, which could be an open
608 zipfile.ZipFile instance, or a str for the dir that contains the files
609 unzipped from a target_files file.
610 repacking: Whether it's trying repack an target_files file after loading the
611 info dict (default: False). If so, it will rewrite a few loaded
612 properties (e.g. selinux_fc, root_dir) to point to the actual files in
613 target_files file. When doing repacking, `input_file` must be a dir.
614
615 Returns:
616 A dict that contains the parsed key/value pairs.
617
618 Raises:
619 AssertionError: On invalid input arguments.
620 ValueError: On malformed input values.
621 """
622 if repacking:
623 assert isinstance(input_file, str), \
624 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700625
Doug Zongkerc9253822014-02-04 12:17:58 -0800626 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000627 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800628
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700629 try:
Michael Runge6e836112014-04-15 17:40:21 -0700630 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700631 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700632 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700633
Tao Bao410ad8b2018-08-24 12:08:38 -0700634 if "recovery_api_version" not in d:
635 raise ValueError("Failed to find 'recovery_api_version'")
636 if "fstab_version" not in d:
637 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800638
Tao Bao410ad8b2018-08-24 12:08:38 -0700639 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700640 # "selinux_fc" properties should point to the file_contexts files
641 # (file_contexts.bin) under META/.
642 for key in d:
643 if key.endswith("selinux_fc"):
644 fc_basename = os.path.basename(d[key])
645 fc_config = os.path.join(input_file, "META", fc_basename)
646 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700647
Daniel Norman72c626f2019-05-13 15:58:14 -0700648 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700649
Tom Cherryd14b8952018-08-09 14:26:00 -0700650 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700651 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700652 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700653 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700654
David Anderson0ec64ac2019-12-06 12:21:18 -0800655 # Redirect {partition}_base_fs_file for each of the named partitions.
656 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
657 key_name = part_name + "_base_fs_file"
658 if key_name not in d:
659 continue
660 basename = os.path.basename(d[key_name])
661 base_fs_file = os.path.join(input_file, "META", basename)
662 if os.path.exists(base_fs_file):
663 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700664 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700665 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800666 "Failed to find %s base fs file: %s", part_name, base_fs_file)
667 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700668
Doug Zongker37974732010-09-16 17:44:38 -0700669 def makeint(key):
670 if key in d:
671 d[key] = int(d[key], 0)
672
673 makeint("recovery_api_version")
674 makeint("blocksize")
675 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700676 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700677 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700678 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700679 makeint("recovery_size")
680 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800681 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700682
Tao Bao765668f2019-10-04 22:03:00 -0700683 # Load recovery fstab if applicable.
684 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800685
Tianjie Xu861f4132018-09-12 11:49:33 -0700686 # Tries to load the build props for all partitions with care_map, including
687 # system and vendor.
688 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800689 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000690 d[partition_prop] = PartitionBuildProps.FromInputFile(
691 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700692 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800693
Tao Bao3ed35d32019-10-07 20:48:48 -0700694 # Set up the salt (based on fingerprint) that will be used when adding AVB
695 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800696 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700697 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800698 for partition in PARTITIONS_WITH_CARE_MAP:
699 fingerprint = build_info.GetPartitionFingerprint(partition)
700 if fingerprint:
701 d["avb_{}_salt".format(partition)] = sha256(fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800702
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700703 return d
704
Tao Baod1de6f32017-03-01 16:38:48 -0800705
Daniel Norman4cc9df62019-07-18 10:11:07 -0700706def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900707 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700708 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900709
Daniel Norman4cc9df62019-07-18 10:11:07 -0700710
711def LoadDictionaryFromFile(file_path):
712 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900713 return LoadDictionaryFromLines(lines)
714
715
Michael Runge6e836112014-04-15 17:40:21 -0700716def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700717 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700718 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700719 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700720 if not line or line.startswith("#"):
721 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700722 if "=" in line:
723 name, value = line.split("=", 1)
724 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700725 return d
726
Tao Baod1de6f32017-03-01 16:38:48 -0800727
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000728class PartitionBuildProps(object):
729 """The class holds the build prop of a particular partition.
730
731 This class loads the build.prop and holds the build properties for a given
732 partition. It also partially recognizes the 'import' statement in the
733 build.prop; and calculates alternative values of some specific build
734 properties during runtime.
735
736 Attributes:
737 input_file: a zipped target-file or an unzipped target-file directory.
738 partition: name of the partition.
739 props_allow_override: a list of build properties to search for the
740 alternative values during runtime.
741 build_props: a dictionary of build properties for the given partition.
742 prop_overrides: a dict of list. And each list holds the overridden values
743 for props_allow_override.
744 """
745
746 def __init__(self, input_file, name):
747 self.input_file = input_file
748 self.partition = name
749 self.props_allow_override = [props.format(name) for props in [
750 'ro.product.{}.name', 'ro.product.{}.device']]
751 self.build_props = {}
752 self.prop_overrides = {}
753
754 @staticmethod
755 def FromDictionary(name, build_props):
756 """Constructs an instance from a build prop dictionary."""
757
758 props = PartitionBuildProps("unknown", name)
759 props.build_props = build_props.copy()
760 return props
761
762 @staticmethod
763 def FromInputFile(input_file, name):
764 """Loads the build.prop file and builds the attributes."""
765
766 data = ''
767 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
768 '{}/build.prop'.format(name.upper())]:
769 try:
770 data = ReadFromInputFile(input_file, prop_file)
771 break
772 except KeyError:
773 logger.warning('Failed to read %s', prop_file)
774
775 props = PartitionBuildProps(input_file, name)
776 props.build_props = LoadDictionaryFromLines(data.split('\n'))
777 return props
778
779 def GetProp(self, prop):
780 return self.build_props.get(prop)
781
782
Tianjie Xucfa86222016-03-07 16:31:19 -0800783def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
784 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700785 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800786 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700787 self.mount_point = mount_point
788 self.fs_type = fs_type
789 self.device = device
790 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700791 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700792
793 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800794 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700795 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700796 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700797 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700798
Tao Baod1de6f32017-03-01 16:38:48 -0800799 assert fstab_version == 2
800
801 d = {}
802 for line in data.split("\n"):
803 line = line.strip()
804 if not line or line.startswith("#"):
805 continue
806
807 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
808 pieces = line.split()
809 if len(pieces) != 5:
810 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
811
812 # Ignore entries that are managed by vold.
813 options = pieces[4]
814 if "voldmanaged=" in options:
815 continue
816
817 # It's a good line, parse it.
818 length = 0
819 options = options.split(",")
820 for i in options:
821 if i.startswith("length="):
822 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800823 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800824 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700825 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800826
Tao Baod1de6f32017-03-01 16:38:48 -0800827 mount_flags = pieces[3]
828 # Honor the SELinux context if present.
829 context = None
830 for i in mount_flags.split(","):
831 if i.startswith("context="):
832 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800833
Tao Baod1de6f32017-03-01 16:38:48 -0800834 mount_point = pieces[1]
835 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
836 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800837
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700838 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700839 # system. Other areas assume system is always at "/system" so point /system
840 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700841 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800842 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700843 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700844 return d
845
846
Tao Bao765668f2019-10-04 22:03:00 -0700847def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
848 """Finds the path to recovery fstab and loads its contents."""
849 # recovery fstab is only meaningful when installing an update via recovery
850 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
851 if info_dict.get('ab_update') == 'true':
852 return None
853
854 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
855 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
856 # cases, since it may load the info_dict from an old build (e.g. when
857 # generating incremental OTAs from that build).
858 system_root_image = info_dict.get('system_root_image') == 'true'
859 if info_dict.get('no_recovery') != 'true':
860 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
861 if isinstance(input_file, zipfile.ZipFile):
862 if recovery_fstab_path not in input_file.namelist():
863 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
864 else:
865 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
866 if not os.path.exists(path):
867 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
868 return LoadRecoveryFSTab(
869 read_helper, info_dict['fstab_version'], recovery_fstab_path,
870 system_root_image)
871
872 if info_dict.get('recovery_as_boot') == 'true':
873 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
874 if isinstance(input_file, zipfile.ZipFile):
875 if recovery_fstab_path not in input_file.namelist():
876 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
877 else:
878 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
879 if not os.path.exists(path):
880 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
881 return LoadRecoveryFSTab(
882 read_helper, info_dict['fstab_version'], recovery_fstab_path,
883 system_root_image)
884
885 return None
886
887
Doug Zongker37974732010-09-16 17:44:38 -0700888def DumpInfoDict(d):
889 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700890 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700891
Dan Albert8b72aef2015-03-23 19:13:21 -0700892
Daniel Norman55417142019-11-25 16:04:36 -0800893def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700894 """Merges dynamic partition info variables.
895
896 Args:
897 framework_dict: The dictionary of dynamic partition info variables from the
898 partial framework target files.
899 vendor_dict: The dictionary of dynamic partition info variables from the
900 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700901
902 Returns:
903 The merged dynamic partition info dictionary.
904 """
905 merged_dict = {}
906 # Partition groups and group sizes are defined by the vendor dict because
907 # these values may vary for each board that uses a shared system image.
908 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800909 framework_dynamic_partition_list = framework_dict.get(
910 "dynamic_partition_list", "")
911 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
912 merged_dict["dynamic_partition_list"] = ("%s %s" % (
913 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700914 for partition_group in merged_dict["super_partition_groups"].split(" "):
915 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800916 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700917 if key not in vendor_dict:
918 raise ValueError("Vendor dict does not contain required key %s." % key)
919 merged_dict[key] = vendor_dict[key]
920
921 # Set the partition group's partition list using a concatenation of the
922 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800923 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700924 merged_dict[key] = (
925 "%s %s" %
926 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530927
928 # Pick virtual ab related flags from vendor dict, if defined.
929 if "virtual_ab" in vendor_dict.keys():
930 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
931 if "virtual_ab_retrofit" in vendor_dict.keys():
932 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700933 return merged_dict
934
935
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800936def AppendAVBSigningArgs(cmd, partition):
937 """Append signing arguments for avbtool."""
938 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
939 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700940 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
941 new_key_path = os.path.join(OPTIONS.search_path, key_path)
942 if os.path.exists(new_key_path):
943 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800944 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
945 if key_path and algorithm:
946 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700947 avb_salt = OPTIONS.info_dict.get("avb_salt")
948 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700949 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700950 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800951
952
Tao Bao765668f2019-10-04 22:03:00 -0700953def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700954 """Returns the VBMeta arguments for partition.
955
956 It sets up the VBMeta argument by including the partition descriptor from the
957 given 'image', or by configuring the partition as a chained partition.
958
959 Args:
960 partition: The name of the partition (e.g. "system").
961 image: The path to the partition image.
962 info_dict: A dict returned by common.LoadInfoDict(). Will use
963 OPTIONS.info_dict if None has been given.
964
965 Returns:
966 A list of VBMeta arguments.
967 """
968 if info_dict is None:
969 info_dict = OPTIONS.info_dict
970
971 # Check if chain partition is used.
972 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +0800973 if not key_path:
974 return ["--include_descriptors_from_image", image]
975
976 # For a non-A/B device, we don't chain /recovery nor include its descriptor
977 # into vbmeta.img. The recovery image will be configured on an independent
978 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
979 # See details at
980 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -0700981 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +0800982 return []
983
984 # Otherwise chain the partition into vbmeta.
985 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
986 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -0700987
988
Tao Bao02a08592018-07-22 12:40:45 -0700989def GetAvbChainedPartitionArg(partition, info_dict, key=None):
990 """Constructs and returns the arg to build or verify a chained partition.
991
992 Args:
993 partition: The partition name.
994 info_dict: The info dict to look up the key info and rollback index
995 location.
996 key: The key to be used for building or verifying the partition. Defaults to
997 the key listed in info_dict.
998
999 Returns:
1000 A string of form "partition:rollback_index_location:key" that can be used to
1001 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001002 """
1003 if key is None:
1004 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001005 if key and not os.path.exists(key) and OPTIONS.search_path:
1006 new_key_path = os.path.join(OPTIONS.search_path, key)
1007 if os.path.exists(new_key_path):
1008 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001009 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001010 rollback_index_location = info_dict[
1011 "avb_" + partition + "_rollback_index_location"]
1012 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1013
1014
Tianjie20dd8f22020-04-19 15:51:16 -07001015def ConstructAftlMakeImageCommands(output_image):
1016 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001017
1018 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001019 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001020 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1021 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1022 'No AFTL manufacturer key provided.'
1023
1024 vbmeta_image = MakeTempFile()
1025 os.rename(output_image, vbmeta_image)
1026 build_info = BuildInfo(OPTIONS.info_dict)
1027 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001028 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001029 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001030 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001031 "--vbmeta_image_path", vbmeta_image,
1032 "--output", output_image,
1033 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001034 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001035 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1036 "--algorithm", "SHA256_RSA4096",
1037 "--padding", "4096"]
1038 if OPTIONS.aftl_signer_helper:
1039 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001040 return aftl_cmd
1041
1042
1043def AddAftlInclusionProof(output_image):
1044 """Appends the aftl inclusion proof to the vbmeta image."""
1045
1046 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001047 RunAndCheckOutput(aftl_cmd)
1048
1049 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1050 output_image, '--transparency_log_pub_keys',
1051 OPTIONS.aftl_key_path]
1052 RunAndCheckOutput(verify_cmd)
1053
1054
Daniel Norman276f0622019-07-26 14:13:51 -07001055def BuildVBMeta(image_path, partitions, name, needed_partitions):
1056 """Creates a VBMeta image.
1057
1058 It generates the requested VBMeta image. The requested image could be for
1059 top-level or chained VBMeta image, which is determined based on the name.
1060
1061 Args:
1062 image_path: The output path for the new VBMeta image.
1063 partitions: A dict that's keyed by partition names with image paths as
1064 values. Only valid partition names are accepted, as listed in
1065 common.AVB_PARTITIONS.
1066 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1067 needed_partitions: Partitions whose descriptors should be included into the
1068 generated VBMeta image.
1069
1070 Raises:
1071 AssertionError: On invalid input args.
1072 """
1073 avbtool = OPTIONS.info_dict["avb_avbtool"]
1074 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1075 AppendAVBSigningArgs(cmd, name)
1076
1077 for partition, path in partitions.items():
1078 if partition not in needed_partitions:
1079 continue
1080 assert (partition in AVB_PARTITIONS or
1081 partition in AVB_VBMETA_PARTITIONS), \
1082 'Unknown partition: {}'.format(partition)
1083 assert os.path.exists(path), \
1084 'Failed to find {} for {}'.format(path, partition)
1085 cmd.extend(GetAvbPartitionArg(partition, path))
1086
1087 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1088 if args and args.strip():
1089 split_args = shlex.split(args)
1090 for index, arg in enumerate(split_args[:-1]):
1091 # Sanity check that the image file exists. Some images might be defined
1092 # as a path relative to source tree, which may not be available at the
1093 # same location when running this script (we have the input target_files
1094 # zip only). For such cases, we additionally scan other locations (e.g.
1095 # IMAGES/, RADIO/, etc) before bailing out.
1096 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001097 chained_image = split_args[index + 1]
1098 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001099 continue
1100 found = False
1101 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1102 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001103 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001104 if os.path.exists(alt_path):
1105 split_args[index + 1] = alt_path
1106 found = True
1107 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001108 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001109 cmd.extend(split_args)
1110
1111 RunAndCheckOutput(cmd)
1112
Tianjie Xueaed60c2020-03-12 00:33:28 -07001113 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001114 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001115 AddAftlInclusionProof(image_path)
1116
Daniel Norman276f0622019-07-26 14:13:51 -07001117
Steve Mucklee1b10862019-07-10 10:49:37 -07001118def _MakeRamdisk(sourcedir, fs_config_file=None):
1119 ramdisk_img = tempfile.NamedTemporaryFile()
1120
1121 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1122 cmd = ["mkbootfs", "-f", fs_config_file,
1123 os.path.join(sourcedir, "RAMDISK")]
1124 else:
1125 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1126 p1 = Run(cmd, stdout=subprocess.PIPE)
1127 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1128
1129 p2.wait()
1130 p1.wait()
1131 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1132 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1133
1134 return ramdisk_img
1135
1136
Steve Muckle9793cf62020-04-08 18:27:00 -07001137def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001138 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001139 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001140
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001141 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001142 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1143 we are building a two-step special image (i.e. building a recovery image to
1144 be loaded into /boot in two-step OTAs).
1145
1146 Return the image data, or None if sourcedir does not appear to contains files
1147 for building the requested image.
1148 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001149
Steve Muckle9793cf62020-04-08 18:27:00 -07001150 # "boot" or "recovery", without extension.
1151 partition_name = os.path.basename(sourcedir).lower()
1152
1153 if partition_name == "recovery":
1154 kernel = "kernel"
1155 else:
1156 kernel = image_name.replace("boot", "kernel")
1157 kernel = kernel.replace(".img","")
1158 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001159 return None
1160
1161 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001162 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001163
Doug Zongkerd5131602012-08-02 14:46:42 -07001164 if info_dict is None:
1165 info_dict = OPTIONS.info_dict
1166
Doug Zongkereef39442009-04-02 12:14:19 -07001167 img = tempfile.NamedTemporaryFile()
1168
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001169 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001170 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001171
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001172 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1173 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1174
Steve Muckle9793cf62020-04-08 18:27:00 -07001175 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001176
Benoit Fradina45a8682014-07-14 21:00:43 +02001177 fn = os.path.join(sourcedir, "second")
1178 if os.access(fn, os.F_OK):
1179 cmd.append("--second")
1180 cmd.append(fn)
1181
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001182 fn = os.path.join(sourcedir, "dtb")
1183 if os.access(fn, os.F_OK):
1184 cmd.append("--dtb")
1185 cmd.append(fn)
1186
Doug Zongker171f1cd2009-06-15 22:36:37 -07001187 fn = os.path.join(sourcedir, "cmdline")
1188 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001189 cmd.append("--cmdline")
1190 cmd.append(open(fn).read().rstrip("\n"))
1191
1192 fn = os.path.join(sourcedir, "base")
1193 if os.access(fn, os.F_OK):
1194 cmd.append("--base")
1195 cmd.append(open(fn).read().rstrip("\n"))
1196
Ying Wang4de6b5b2010-08-25 14:29:34 -07001197 fn = os.path.join(sourcedir, "pagesize")
1198 if os.access(fn, os.F_OK):
1199 cmd.append("--pagesize")
1200 cmd.append(open(fn).read().rstrip("\n"))
1201
Steve Mucklef84668e2020-03-16 19:13:46 -07001202 if partition_name == "recovery":
1203 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301204 if not args:
1205 # Fall back to "mkbootimg_args" for recovery image
1206 # in case "recovery_mkbootimg_args" is not set.
1207 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001208 else:
1209 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001210 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001211 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001212
Tao Bao76def242017-11-21 09:25:31 -08001213 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001214 if args and args.strip():
1215 cmd.extend(shlex.split(args))
1216
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001217 if has_ramdisk:
1218 cmd.extend(["--ramdisk", ramdisk_img.name])
1219
Tao Baod95e9fd2015-03-29 23:07:41 -07001220 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001221 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001222 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001223 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001224 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001225 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001226
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001227 if partition_name == "recovery":
1228 if info_dict.get("include_recovery_dtbo") == "true":
1229 fn = os.path.join(sourcedir, "recovery_dtbo")
1230 cmd.extend(["--recovery_dtbo", fn])
1231 if info_dict.get("include_recovery_acpio") == "true":
1232 fn = os.path.join(sourcedir, "recovery_acpio")
1233 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001234
Tao Bao986ee862018-10-04 15:46:16 -07001235 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001236
Tao Bao76def242017-11-21 09:25:31 -08001237 if (info_dict.get("boot_signer") == "true" and
1238 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001239 # Hard-code the path as "/boot" for two-step special recovery image (which
1240 # will be loaded into /boot during the two-step OTA).
1241 if two_step_image:
1242 path = "/boot"
1243 else:
Tao Baobf70c312017-07-11 17:27:55 -07001244 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001245 cmd = [OPTIONS.boot_signer_path]
1246 cmd.extend(OPTIONS.boot_signer_args)
1247 cmd.extend([path, img.name,
1248 info_dict["verity_key"] + ".pk8",
1249 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001250 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001251
Tao Baod95e9fd2015-03-29 23:07:41 -07001252 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001253 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001254 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001255 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001256 # We have switched from the prebuilt futility binary to using the tool
1257 # (futility-host) built from the source. Override the setting in the old
1258 # TF.zip.
1259 futility = info_dict["futility"]
1260 if futility.startswith("prebuilts/"):
1261 futility = "futility-host"
1262 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001263 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001264 info_dict["vboot_key"] + ".vbprivk",
1265 info_dict["vboot_subkey"] + ".vbprivk",
1266 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001267 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001268 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001269
Tao Baof3282b42015-04-01 11:21:55 -07001270 # Clean up the temp files.
1271 img_unsigned.close()
1272 img_keyblock.close()
1273
David Zeuthen8fecb282017-12-01 16:24:01 -05001274 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001275 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001276 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001277 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001278 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001279 "--partition_size", str(part_size), "--partition_name",
1280 partition_name]
1281 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001282 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001283 if args and args.strip():
1284 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001285 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001286
1287 img.seek(os.SEEK_SET, 0)
1288 data = img.read()
1289
1290 if has_ramdisk:
1291 ramdisk_img.close()
1292 img.close()
1293
1294 return data
1295
1296
Doug Zongkerd5131602012-08-02 14:46:42 -07001297def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001298 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001299 """Return a File object with the desired bootable image.
1300
1301 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1302 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1303 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001304
Doug Zongker55d93282011-01-25 17:03:34 -08001305 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1306 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001307 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001308 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001309
1310 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1311 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001312 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001313 return File.FromLocalFile(name, prebuilt_path)
1314
Tao Bao32fcdab2018-10-12 10:30:39 -07001315 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001316
1317 if info_dict is None:
1318 info_dict = OPTIONS.info_dict
1319
1320 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001321 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1322 # for recovery.
1323 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1324 prebuilt_name != "boot.img" or
1325 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001326
Doug Zongker6f1d0312014-08-22 08:07:12 -07001327 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001328 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001329 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001330 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001331 if data:
1332 return File(name, data)
1333 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001334
Doug Zongkereef39442009-04-02 12:14:19 -07001335
Steve Mucklee1b10862019-07-10 10:49:37 -07001336def _BuildVendorBootImage(sourcedir, info_dict=None):
1337 """Build a vendor boot image from the specified sourcedir.
1338
1339 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1340 turn them into a vendor boot image.
1341
1342 Return the image data, or None if sourcedir does not appear to contains files
1343 for building the requested image.
1344 """
1345
1346 if info_dict is None:
1347 info_dict = OPTIONS.info_dict
1348
1349 img = tempfile.NamedTemporaryFile()
1350
1351 ramdisk_img = _MakeRamdisk(sourcedir)
1352
1353 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1354 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1355
1356 cmd = [mkbootimg]
1357
1358 fn = os.path.join(sourcedir, "dtb")
1359 if os.access(fn, os.F_OK):
1360 cmd.append("--dtb")
1361 cmd.append(fn)
1362
1363 fn = os.path.join(sourcedir, "vendor_cmdline")
1364 if os.access(fn, os.F_OK):
1365 cmd.append("--vendor_cmdline")
1366 cmd.append(open(fn).read().rstrip("\n"))
1367
1368 fn = os.path.join(sourcedir, "base")
1369 if os.access(fn, os.F_OK):
1370 cmd.append("--base")
1371 cmd.append(open(fn).read().rstrip("\n"))
1372
1373 fn = os.path.join(sourcedir, "pagesize")
1374 if os.access(fn, os.F_OK):
1375 cmd.append("--pagesize")
1376 cmd.append(open(fn).read().rstrip("\n"))
1377
1378 args = info_dict.get("mkbootimg_args")
1379 if args and args.strip():
1380 cmd.extend(shlex.split(args))
1381
1382 args = info_dict.get("mkbootimg_version_args")
1383 if args and args.strip():
1384 cmd.extend(shlex.split(args))
1385
1386 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1387 cmd.extend(["--vendor_boot", img.name])
1388
1389 RunAndCheckOutput(cmd)
1390
1391 # AVB: if enabled, calculate and add hash.
1392 if info_dict.get("avb_enable") == "true":
1393 avbtool = info_dict["avb_avbtool"]
1394 part_size = info_dict["vendor_boot_size"]
1395 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001396 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001397 AppendAVBSigningArgs(cmd, "vendor_boot")
1398 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1399 if args and args.strip():
1400 cmd.extend(shlex.split(args))
1401 RunAndCheckOutput(cmd)
1402
1403 img.seek(os.SEEK_SET, 0)
1404 data = img.read()
1405
1406 ramdisk_img.close()
1407 img.close()
1408
1409 return data
1410
1411
1412def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1413 info_dict=None):
1414 """Return a File object with the desired vendor boot image.
1415
1416 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1417 the source files in 'unpack_dir'/'tree_subdir'."""
1418
1419 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1420 if os.path.exists(prebuilt_path):
1421 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1422 return File.FromLocalFile(name, prebuilt_path)
1423
1424 logger.info("building image from target_files %s...", tree_subdir)
1425
1426 if info_dict is None:
1427 info_dict = OPTIONS.info_dict
1428
1429 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1430 if data:
1431 return File(name, data)
1432 return None
1433
1434
Narayan Kamatha07bf042017-08-14 14:49:21 +01001435def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001436 """Gunzips the given gzip compressed file to a given output file."""
1437 with gzip.open(in_filename, "rb") as in_file, \
1438 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001439 shutil.copyfileobj(in_file, out_file)
1440
1441
Tao Bao0ff15de2019-03-20 11:26:06 -07001442def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001443 """Unzips the archive to the given directory.
1444
1445 Args:
1446 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001447 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001448 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1449 archvie. Non-matching patterns will be filtered out. If there's no match
1450 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001451 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001452 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001453 if patterns is not None:
1454 # Filter out non-matching patterns. unzip will complain otherwise.
1455 with zipfile.ZipFile(filename) as input_zip:
1456 names = input_zip.namelist()
1457 filtered = [
1458 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1459
1460 # There isn't any matching files. Don't unzip anything.
1461 if not filtered:
1462 return
1463 cmd.extend(filtered)
1464
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001465 RunAndCheckOutput(cmd)
1466
1467
Doug Zongker75f17362009-12-08 13:46:44 -08001468def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001469 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001470
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001471 Args:
1472 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1473 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1474
1475 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1476 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001477
Tao Bao1c830bf2017-12-25 10:43:47 -08001478 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001479 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001480 """
Doug Zongkereef39442009-04-02 12:14:19 -07001481
Tao Bao1c830bf2017-12-25 10:43:47 -08001482 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001483 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1484 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001485 UnzipToDir(m.group(1), tmp, pattern)
1486 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001487 filename = m.group(1)
1488 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001489 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001490
Tao Baodba59ee2018-01-09 13:21:02 -08001491 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001492
1493
Yifan Hong8a66a712019-04-04 15:37:57 -07001494def GetUserImage(which, tmpdir, input_zip,
1495 info_dict=None,
1496 allow_shared_blocks=None,
1497 hashtree_info_generator=None,
1498 reset_file_map=False):
1499 """Returns an Image object suitable for passing to BlockImageDiff.
1500
1501 This function loads the specified image from the given path. If the specified
1502 image is sparse, it also performs additional processing for OTA purpose. For
1503 example, it always adds block 0 to clobbered blocks list. It also detects
1504 files that cannot be reconstructed from the block list, for whom we should
1505 avoid applying imgdiff.
1506
1507 Args:
1508 which: The partition name.
1509 tmpdir: The directory that contains the prebuilt image and block map file.
1510 input_zip: The target-files ZIP archive.
1511 info_dict: The dict to be looked up for relevant info.
1512 allow_shared_blocks: If image is sparse, whether having shared blocks is
1513 allowed. If none, it is looked up from info_dict.
1514 hashtree_info_generator: If present and image is sparse, generates the
1515 hashtree_info for this sparse image.
1516 reset_file_map: If true and image is sparse, reset file map before returning
1517 the image.
1518 Returns:
1519 A Image object. If it is a sparse image and reset_file_map is False, the
1520 image will have file_map info loaded.
1521 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001522 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001523 info_dict = LoadInfoDict(input_zip)
1524
1525 is_sparse = info_dict.get("extfs_sparse_flag")
1526
1527 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1528 # shared blocks (i.e. some blocks will show up in multiple files' block
1529 # list). We can only allocate such shared blocks to the first "owner", and
1530 # disable imgdiff for all later occurrences.
1531 if allow_shared_blocks is None:
1532 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1533
1534 if is_sparse:
1535 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1536 hashtree_info_generator)
1537 if reset_file_map:
1538 img.ResetFileMap()
1539 return img
1540 else:
1541 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1542
1543
1544def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1545 """Returns a Image object suitable for passing to BlockImageDiff.
1546
1547 This function loads the specified non-sparse image from the given path.
1548
1549 Args:
1550 which: The partition name.
1551 tmpdir: The directory that contains the prebuilt image and block map file.
1552 Returns:
1553 A Image object.
1554 """
1555 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1556 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1557
1558 # The image and map files must have been created prior to calling
1559 # ota_from_target_files.py (since LMP).
1560 assert os.path.exists(path) and os.path.exists(mappath)
1561
Tianjie Xu41976c72019-07-03 13:57:01 -07001562 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1563
Yifan Hong8a66a712019-04-04 15:37:57 -07001564
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001565def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1566 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001567 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1568
1569 This function loads the specified sparse image from the given path, and
1570 performs additional processing for OTA purpose. For example, it always adds
1571 block 0 to clobbered blocks list. It also detects files that cannot be
1572 reconstructed from the block list, for whom we should avoid applying imgdiff.
1573
1574 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001575 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001576 tmpdir: The directory that contains the prebuilt image and block map file.
1577 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001578 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001579 hashtree_info_generator: If present, generates the hashtree_info for this
1580 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001581 Returns:
1582 A SparseImage object, with file_map info loaded.
1583 """
Tao Baoc765cca2018-01-31 17:32:40 -08001584 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1585 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1586
1587 # The image and map files must have been created prior to calling
1588 # ota_from_target_files.py (since LMP).
1589 assert os.path.exists(path) and os.path.exists(mappath)
1590
1591 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1592 # it to clobbered_blocks so that it will be written to the target
1593 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1594 clobbered_blocks = "0"
1595
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001596 image = sparse_img.SparseImage(
1597 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1598 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001599
1600 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1601 # if they contain all zeros. We can't reconstruct such a file from its block
1602 # list. Tag such entries accordingly. (Bug: 65213616)
1603 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001604 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001605 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001606 continue
1607
Tom Cherryd14b8952018-08-09 14:26:00 -07001608 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1609 # filename listed in system.map may contain an additional leading slash
1610 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1611 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001612 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001613
Tom Cherryd14b8952018-08-09 14:26:00 -07001614 # Special handling another case, where files not under /system
1615 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001616 if which == 'system' and not arcname.startswith('SYSTEM'):
1617 arcname = 'ROOT/' + arcname
1618
1619 assert arcname in input_zip.namelist(), \
1620 "Failed to find the ZIP entry for {}".format(entry)
1621
Tao Baoc765cca2018-01-31 17:32:40 -08001622 info = input_zip.getinfo(arcname)
1623 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001624
1625 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001626 # image, check the original block list to determine its completeness. Note
1627 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001628 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001629 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001630
Tao Baoc765cca2018-01-31 17:32:40 -08001631 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1632 ranges.extra['incomplete'] = True
1633
1634 return image
1635
1636
Doug Zongkereef39442009-04-02 12:14:19 -07001637def GetKeyPasswords(keylist):
1638 """Given a list of keys, prompt the user to enter passwords for
1639 those which require them. Return a {key: password} dict. password
1640 will be None if the key has no password."""
1641
Doug Zongker8ce7c252009-05-22 13:34:54 -07001642 no_passwords = []
1643 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001644 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001645 devnull = open("/dev/null", "w+b")
1646 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001647 # We don't need a password for things that aren't really keys.
1648 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001649 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001650 continue
1651
T.R. Fullhart37e10522013-03-18 10:31:26 -07001652 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001653 "-inform", "DER", "-nocrypt"],
1654 stdin=devnull.fileno(),
1655 stdout=devnull.fileno(),
1656 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001657 p.communicate()
1658 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001659 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001660 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001661 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001662 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1663 "-inform", "DER", "-passin", "pass:"],
1664 stdin=devnull.fileno(),
1665 stdout=devnull.fileno(),
1666 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001667 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001668 if p.returncode == 0:
1669 # Encrypted key with empty string as password.
1670 key_passwords[k] = ''
1671 elif stderr.startswith('Error decrypting key'):
1672 # Definitely encrypted key.
1673 # It would have said "Error reading key" if it didn't parse correctly.
1674 need_passwords.append(k)
1675 else:
1676 # Potentially, a type of key that openssl doesn't understand.
1677 # We'll let the routines in signapk.jar handle it.
1678 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001679 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001680
T.R. Fullhart37e10522013-03-18 10:31:26 -07001681 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001682 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001683 return key_passwords
1684
1685
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001686def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001687 """Gets the minSdkVersion declared in the APK.
1688
changho.shin0f125362019-07-08 10:59:00 +09001689 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001690 This can be both a decimal number (API Level) or a codename.
1691
1692 Args:
1693 apk_name: The APK filename.
1694
1695 Returns:
1696 The parsed SDK version string.
1697
1698 Raises:
1699 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001700 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001701 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001702 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001703 stderr=subprocess.PIPE)
1704 stdoutdata, stderrdata = proc.communicate()
1705 if proc.returncode != 0:
1706 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001707 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001708 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001709
Tao Baof47bf0f2018-03-21 23:28:51 -07001710 for line in stdoutdata.split("\n"):
1711 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001712 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1713 if m:
1714 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001715 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001716
1717
1718def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001719 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001720
Tao Baof47bf0f2018-03-21 23:28:51 -07001721 If minSdkVersion is set to a codename, it is translated to a number using the
1722 provided map.
1723
1724 Args:
1725 apk_name: The APK filename.
1726
1727 Returns:
1728 The parsed SDK version number.
1729
1730 Raises:
1731 ExternalError: On failing to get the min SDK version number.
1732 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001733 version = GetMinSdkVersion(apk_name)
1734 try:
1735 return int(version)
1736 except ValueError:
1737 # Not a decimal number. Codename?
1738 if version in codename_to_api_level_map:
1739 return codename_to_api_level_map[version]
1740 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001741 raise ExternalError(
1742 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1743 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001744
1745
1746def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001747 codename_to_api_level_map=None, whole_file=False,
1748 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001749 """Sign the input_name zip/jar/apk, producing output_name. Use the
1750 given key and password (the latter may be None if the key does not
1751 have a password.
1752
Doug Zongker951495f2009-08-14 12:44:19 -07001753 If whole_file is true, use the "-w" option to SignApk to embed a
1754 signature that covers the whole file in the archive comment of the
1755 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001756
1757 min_api_level is the API Level (int) of the oldest platform this file may end
1758 up on. If not specified for an APK, the API Level is obtained by interpreting
1759 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1760
1761 codename_to_api_level_map is needed to translate the codename which may be
1762 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001763
1764 Caller may optionally specify extra args to be passed to SignApk, which
1765 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001766 """
Tao Bao76def242017-11-21 09:25:31 -08001767 if codename_to_api_level_map is None:
1768 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001769 if extra_signapk_args is None:
1770 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001771
Alex Klyubin9667b182015-12-10 13:38:50 -08001772 java_library_path = os.path.join(
1773 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1774
Tao Baoe95540e2016-11-08 12:08:53 -08001775 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1776 ["-Djava.library.path=" + java_library_path,
1777 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001778 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001779 if whole_file:
1780 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001781
1782 min_sdk_version = min_api_level
1783 if min_sdk_version is None:
1784 if not whole_file:
1785 min_sdk_version = GetMinSdkVersionInt(
1786 input_name, codename_to_api_level_map)
1787 if min_sdk_version is not None:
1788 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1789
T.R. Fullhart37e10522013-03-18 10:31:26 -07001790 cmd.extend([key + OPTIONS.public_key_suffix,
1791 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001792 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001793
Tao Bao73dd4f42018-10-04 16:25:33 -07001794 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001795 if password is not None:
1796 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001797 stdoutdata, _ = proc.communicate(password)
1798 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001799 raise ExternalError(
1800 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001801 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001802
Doug Zongkereef39442009-04-02 12:14:19 -07001803
Doug Zongker37974732010-09-16 17:44:38 -07001804def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001805 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001806
Tao Bao9dd909e2017-11-14 11:27:32 -08001807 For non-AVB images, raise exception if the data is too big. Print a warning
1808 if the data is nearing the maximum size.
1809
1810 For AVB images, the actual image size should be identical to the limit.
1811
1812 Args:
1813 data: A string that contains all the data for the partition.
1814 target: The partition name. The ".img" suffix is optional.
1815 info_dict: The dict to be looked up for relevant info.
1816 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001817 if target.endswith(".img"):
1818 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001819 mount_point = "/" + target
1820
Ying Wangf8824af2014-06-03 14:07:27 -07001821 fs_type = None
1822 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001823 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001824 if mount_point == "/userdata":
1825 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001826 p = info_dict["fstab"][mount_point]
1827 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001828 device = p.device
1829 if "/" in device:
1830 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001831 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001832 if not fs_type or not limit:
1833 return
Doug Zongkereef39442009-04-02 12:14:19 -07001834
Andrew Boie0f9aec82012-02-14 09:32:52 -08001835 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001836 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1837 # path.
1838 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1839 if size != limit:
1840 raise ExternalError(
1841 "Mismatching image size for %s: expected %d actual %d" % (
1842 target, limit, size))
1843 else:
1844 pct = float(size) * 100.0 / limit
1845 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1846 if pct >= 99.0:
1847 raise ExternalError(msg)
1848 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001849 logger.warning("\n WARNING: %s\n", msg)
1850 else:
1851 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001852
1853
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001854def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001855 """Parses the APK certs info from a given target-files zip.
1856
1857 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1858 tuple with the following elements: (1) a dictionary that maps packages to
1859 certs (based on the "certificate" and "private_key" attributes in the file;
1860 (2) a string representing the extension of compressed APKs in the target files
1861 (e.g ".gz", ".bro").
1862
1863 Args:
1864 tf_zip: The input target_files ZipFile (already open).
1865
1866 Returns:
1867 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1868 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1869 no compressed APKs.
1870 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001871 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001872 compressed_extension = None
1873
Tao Bao0f990332017-09-08 19:02:54 -07001874 # META/apkcerts.txt contains the info for _all_ the packages known at build
1875 # time. Filter out the ones that are not installed.
1876 installed_files = set()
1877 for name in tf_zip.namelist():
1878 basename = os.path.basename(name)
1879 if basename:
1880 installed_files.add(basename)
1881
Tao Baoda30cfa2017-12-01 16:19:46 -08001882 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001883 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001884 if not line:
1885 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001886 m = re.match(
1887 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001888 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1889 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001890 line)
1891 if not m:
1892 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001893
Tao Bao818ddf52018-01-05 11:17:34 -08001894 matches = m.groupdict()
1895 cert = matches["CERT"]
1896 privkey = matches["PRIVKEY"]
1897 name = matches["NAME"]
1898 this_compressed_extension = matches["COMPRESSED"]
1899
1900 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1901 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1902 if cert in SPECIAL_CERT_STRINGS and not privkey:
1903 certmap[name] = cert
1904 elif (cert.endswith(OPTIONS.public_key_suffix) and
1905 privkey.endswith(OPTIONS.private_key_suffix) and
1906 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1907 certmap[name] = cert[:-public_key_suffix_len]
1908 else:
1909 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1910
1911 if not this_compressed_extension:
1912 continue
1913
1914 # Only count the installed files.
1915 filename = name + '.' + this_compressed_extension
1916 if filename not in installed_files:
1917 continue
1918
1919 # Make sure that all the values in the compression map have the same
1920 # extension. We don't support multiple compression methods in the same
1921 # system image.
1922 if compressed_extension:
1923 if this_compressed_extension != compressed_extension:
1924 raise ValueError(
1925 "Multiple compressed extensions: {} vs {}".format(
1926 compressed_extension, this_compressed_extension))
1927 else:
1928 compressed_extension = this_compressed_extension
1929
1930 return (certmap,
1931 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001932
1933
Doug Zongkereef39442009-04-02 12:14:19 -07001934COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001935Global options
1936
1937 -p (--path) <dir>
1938 Prepend <dir>/bin to the list of places to search for binaries run by this
1939 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001940
Doug Zongker05d3dea2009-06-22 11:32:31 -07001941 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001942 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001943
Tao Bao30df8b42018-04-23 15:32:53 -07001944 -x (--extra) <key=value>
1945 Add a key/value pair to the 'extras' dict, which device-specific extension
1946 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001947
Doug Zongkereef39442009-04-02 12:14:19 -07001948 -v (--verbose)
1949 Show command lines being executed.
1950
1951 -h (--help)
1952 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001953
1954 --logfile <file>
1955 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001956"""
1957
1958def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001959 print(docstring.rstrip("\n"))
1960 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001961
1962
1963def ParseOptions(argv,
1964 docstring,
1965 extra_opts="", extra_long_opts=(),
1966 extra_option_handler=None):
1967 """Parse the options in argv and return any arguments that aren't
1968 flags. docstring is the calling module's docstring, to be displayed
1969 for errors and -h. extra_opts and extra_long_opts are for flags
1970 defined by the caller, which are processed by passing them to
1971 extra_option_handler."""
1972
1973 try:
1974 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001975 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001976 ["help", "verbose", "path=", "signapk_path=",
1977 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08001978 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001979 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1980 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07001981 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
1982 "aftl_key_path=", "aftl_manufacturer_key_path=",
1983 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001984 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001985 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001986 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001987 sys.exit(2)
1988
Doug Zongkereef39442009-04-02 12:14:19 -07001989 for o, a in opts:
1990 if o in ("-h", "--help"):
1991 Usage(docstring)
1992 sys.exit()
1993 elif o in ("-v", "--verbose"):
1994 OPTIONS.verbose = True
1995 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001996 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001997 elif o in ("--signapk_path",):
1998 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001999 elif o in ("--signapk_shared_library_path",):
2000 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002001 elif o in ("--extra_signapk_args",):
2002 OPTIONS.extra_signapk_args = shlex.split(a)
2003 elif o in ("--java_path",):
2004 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002005 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002006 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002007 elif o in ("--android_jar_path",):
2008 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002009 elif o in ("--public_key_suffix",):
2010 OPTIONS.public_key_suffix = a
2011 elif o in ("--private_key_suffix",):
2012 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002013 elif o in ("--boot_signer_path",):
2014 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002015 elif o in ("--boot_signer_args",):
2016 OPTIONS.boot_signer_args = shlex.split(a)
2017 elif o in ("--verity_signer_path",):
2018 OPTIONS.verity_signer_path = a
2019 elif o in ("--verity_signer_args",):
2020 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002021 elif o in ("--aftl_tool_path",):
2022 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002023 elif o in ("--aftl_server",):
2024 OPTIONS.aftl_server = a
2025 elif o in ("--aftl_key_path",):
2026 OPTIONS.aftl_key_path = a
2027 elif o in ("--aftl_manufacturer_key_path",):
2028 OPTIONS.aftl_manufacturer_key_path = a
2029 elif o in ("--aftl_signer_helper",):
2030 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002031 elif o in ("-s", "--device_specific"):
2032 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002033 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002034 key, value = a.split("=", 1)
2035 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002036 elif o in ("--logfile",):
2037 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002038 else:
2039 if extra_option_handler is None or not extra_option_handler(o, a):
2040 assert False, "unknown option \"%s\"" % (o,)
2041
Doug Zongker85448772014-09-09 14:59:20 -07002042 if OPTIONS.search_path:
2043 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2044 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002045
2046 return args
2047
2048
Tao Bao4c851b12016-09-19 13:54:38 -07002049def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002050 """Make a temp file and add it to the list of things to be deleted
2051 when Cleanup() is called. Return the filename."""
2052 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2053 os.close(fd)
2054 OPTIONS.tempfiles.append(fn)
2055 return fn
2056
2057
Tao Bao1c830bf2017-12-25 10:43:47 -08002058def MakeTempDir(prefix='tmp', suffix=''):
2059 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2060
2061 Returns:
2062 The absolute pathname of the new directory.
2063 """
2064 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2065 OPTIONS.tempfiles.append(dir_name)
2066 return dir_name
2067
2068
Doug Zongkereef39442009-04-02 12:14:19 -07002069def Cleanup():
2070 for i in OPTIONS.tempfiles:
2071 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002072 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002073 else:
2074 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002075 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002076
2077
2078class PasswordManager(object):
2079 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002080 self.editor = os.getenv("EDITOR")
2081 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002082
2083 def GetPasswords(self, items):
2084 """Get passwords corresponding to each string in 'items',
2085 returning a dict. (The dict may have keys in addition to the
2086 values in 'items'.)
2087
2088 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2089 user edit that file to add more needed passwords. If no editor is
2090 available, or $ANDROID_PW_FILE isn't define, prompts the user
2091 interactively in the ordinary way.
2092 """
2093
2094 current = self.ReadFile()
2095
2096 first = True
2097 while True:
2098 missing = []
2099 for i in items:
2100 if i not in current or not current[i]:
2101 missing.append(i)
2102 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002103 if not missing:
2104 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002105
2106 for i in missing:
2107 current[i] = ""
2108
2109 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002110 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002111 if sys.version_info[0] >= 3:
2112 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002113 answer = raw_input("try to edit again? [y]> ").strip()
2114 if answer and answer[0] not in 'yY':
2115 raise RuntimeError("key passwords unavailable")
2116 first = False
2117
2118 current = self.UpdateAndReadFile(current)
2119
Dan Albert8b72aef2015-03-23 19:13:21 -07002120 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002121 """Prompt the user to enter a value (password) for each key in
2122 'current' whose value is fales. Returns a new dict with all the
2123 values.
2124 """
2125 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002126 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002127 if v:
2128 result[k] = v
2129 else:
2130 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002131 result[k] = getpass.getpass(
2132 "Enter password for %s key> " % k).strip()
2133 if result[k]:
2134 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002135 return result
2136
2137 def UpdateAndReadFile(self, current):
2138 if not self.editor or not self.pwfile:
2139 return self.PromptResult(current)
2140
2141 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002142 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002143 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2144 f.write("# (Additional spaces are harmless.)\n\n")
2145
2146 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002147 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002148 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002149 f.write("[[[ %s ]]] %s\n" % (v, k))
2150 if not v and first_line is None:
2151 # position cursor on first line with no password.
2152 first_line = i + 4
2153 f.close()
2154
Tao Bao986ee862018-10-04 15:46:16 -07002155 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002156
2157 return self.ReadFile()
2158
2159 def ReadFile(self):
2160 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002161 if self.pwfile is None:
2162 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002163 try:
2164 f = open(self.pwfile, "r")
2165 for line in f:
2166 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002167 if not line or line[0] == '#':
2168 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002169 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2170 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002171 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002172 else:
2173 result[m.group(2)] = m.group(1)
2174 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002175 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002176 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002177 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002178 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002179
2180
Dan Albert8e0178d2015-01-27 15:53:15 -08002181def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2182 compress_type=None):
2183 import datetime
2184
2185 # http://b/18015246
2186 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2187 # for files larger than 2GiB. We can work around this by adjusting their
2188 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2189 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2190 # it isn't clear to me exactly what circumstances cause this).
2191 # `zipfile.write()` must be used directly to work around this.
2192 #
2193 # This mess can be avoided if we port to python3.
2194 saved_zip64_limit = zipfile.ZIP64_LIMIT
2195 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2196
2197 if compress_type is None:
2198 compress_type = zip_file.compression
2199 if arcname is None:
2200 arcname = filename
2201
2202 saved_stat = os.stat(filename)
2203
2204 try:
2205 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2206 # file to be zipped and reset it when we're done.
2207 os.chmod(filename, perms)
2208
2209 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002210 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2211 # intentional. zip stores datetimes in local time without a time zone
2212 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2213 # in the zip archive.
2214 local_epoch = datetime.datetime.fromtimestamp(0)
2215 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002216 os.utime(filename, (timestamp, timestamp))
2217
2218 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2219 finally:
2220 os.chmod(filename, saved_stat.st_mode)
2221 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2222 zipfile.ZIP64_LIMIT = saved_zip64_limit
2223
2224
Tao Bao58c1b962015-05-20 09:32:18 -07002225def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002226 compress_type=None):
2227 """Wrap zipfile.writestr() function to work around the zip64 limit.
2228
2229 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2230 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2231 when calling crc32(bytes).
2232
2233 But it still works fine to write a shorter string into a large zip file.
2234 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2235 when we know the string won't be too long.
2236 """
2237
2238 saved_zip64_limit = zipfile.ZIP64_LIMIT
2239 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2240
2241 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2242 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002243 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002244 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002245 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002246 else:
Tao Baof3282b42015-04-01 11:21:55 -07002247 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002248 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2249 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2250 # such a case (since
2251 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2252 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2253 # permission bits. We follow the logic in Python 3 to get consistent
2254 # behavior between using the two versions.
2255 if not zinfo.external_attr:
2256 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002257
2258 # If compress_type is given, it overrides the value in zinfo.
2259 if compress_type is not None:
2260 zinfo.compress_type = compress_type
2261
Tao Bao58c1b962015-05-20 09:32:18 -07002262 # If perms is given, it has a priority.
2263 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002264 # If perms doesn't set the file type, mark it as a regular file.
2265 if perms & 0o770000 == 0:
2266 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002267 zinfo.external_attr = perms << 16
2268
Tao Baof3282b42015-04-01 11:21:55 -07002269 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002270 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2271
Dan Albert8b72aef2015-03-23 19:13:21 -07002272 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002273 zipfile.ZIP64_LIMIT = saved_zip64_limit
2274
2275
Tao Bao89d7ab22017-12-14 17:05:33 -08002276def ZipDelete(zip_filename, entries):
2277 """Deletes entries from a ZIP file.
2278
2279 Since deleting entries from a ZIP file is not supported, it shells out to
2280 'zip -d'.
2281
2282 Args:
2283 zip_filename: The name of the ZIP file.
2284 entries: The name of the entry, or the list of names to be deleted.
2285
2286 Raises:
2287 AssertionError: In case of non-zero return from 'zip'.
2288 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002289 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002290 entries = [entries]
2291 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002292 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002293
2294
Tao Baof3282b42015-04-01 11:21:55 -07002295def ZipClose(zip_file):
2296 # http://b/18015246
2297 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2298 # central directory.
2299 saved_zip64_limit = zipfile.ZIP64_LIMIT
2300 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2301
2302 zip_file.close()
2303
2304 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002305
2306
2307class DeviceSpecificParams(object):
2308 module = None
2309 def __init__(self, **kwargs):
2310 """Keyword arguments to the constructor become attributes of this
2311 object, which is passed to all functions in the device-specific
2312 module."""
Tao Bao38884282019-07-10 22:20:56 -07002313 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002314 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002315 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002316
2317 if self.module is None:
2318 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002319 if not path:
2320 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002321 try:
2322 if os.path.isdir(path):
2323 info = imp.find_module("releasetools", [path])
2324 else:
2325 d, f = os.path.split(path)
2326 b, x = os.path.splitext(f)
2327 if x == ".py":
2328 f = b
2329 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002330 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002331 self.module = imp.load_module("device_specific", *info)
2332 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002333 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002334
2335 def _DoCall(self, function_name, *args, **kwargs):
2336 """Call the named function in the device-specific module, passing
2337 the given args and kwargs. The first argument to the call will be
2338 the DeviceSpecific object itself. If there is no module, or the
2339 module does not define the function, return the value of the
2340 'default' kwarg (which itself defaults to None)."""
2341 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002342 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002343 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2344
2345 def FullOTA_Assertions(self):
2346 """Called after emitting the block of assertions at the top of a
2347 full OTA package. Implementations can add whatever additional
2348 assertions they like."""
2349 return self._DoCall("FullOTA_Assertions")
2350
Doug Zongkere5ff5902012-01-17 10:55:37 -08002351 def FullOTA_InstallBegin(self):
2352 """Called at the start of full OTA installation."""
2353 return self._DoCall("FullOTA_InstallBegin")
2354
Yifan Hong10c530d2018-12-27 17:34:18 -08002355 def FullOTA_GetBlockDifferences(self):
2356 """Called during full OTA installation and verification.
2357 Implementation should return a list of BlockDifference objects describing
2358 the update on each additional partitions.
2359 """
2360 return self._DoCall("FullOTA_GetBlockDifferences")
2361
Doug Zongker05d3dea2009-06-22 11:32:31 -07002362 def FullOTA_InstallEnd(self):
2363 """Called at the end of full OTA installation; typically this is
2364 used to install the image for the device's baseband processor."""
2365 return self._DoCall("FullOTA_InstallEnd")
2366
2367 def IncrementalOTA_Assertions(self):
2368 """Called after emitting the block of assertions at the top of an
2369 incremental OTA package. Implementations can add whatever
2370 additional assertions they like."""
2371 return self._DoCall("IncrementalOTA_Assertions")
2372
Doug Zongkere5ff5902012-01-17 10:55:37 -08002373 def IncrementalOTA_VerifyBegin(self):
2374 """Called at the start of the verification phase of incremental
2375 OTA installation; additional checks can be placed here to abort
2376 the script before any changes are made."""
2377 return self._DoCall("IncrementalOTA_VerifyBegin")
2378
Doug Zongker05d3dea2009-06-22 11:32:31 -07002379 def IncrementalOTA_VerifyEnd(self):
2380 """Called at the end of the verification phase of incremental OTA
2381 installation; additional checks can be placed here to abort the
2382 script before any changes are made."""
2383 return self._DoCall("IncrementalOTA_VerifyEnd")
2384
Doug Zongkere5ff5902012-01-17 10:55:37 -08002385 def IncrementalOTA_InstallBegin(self):
2386 """Called at the start of incremental OTA installation (after
2387 verification is complete)."""
2388 return self._DoCall("IncrementalOTA_InstallBegin")
2389
Yifan Hong10c530d2018-12-27 17:34:18 -08002390 def IncrementalOTA_GetBlockDifferences(self):
2391 """Called during incremental OTA installation and verification.
2392 Implementation should return a list of BlockDifference objects describing
2393 the update on each additional partitions.
2394 """
2395 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2396
Doug Zongker05d3dea2009-06-22 11:32:31 -07002397 def IncrementalOTA_InstallEnd(self):
2398 """Called at the end of incremental OTA installation; typically
2399 this is used to install the image for the device's baseband
2400 processor."""
2401 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002402
Tao Bao9bc6bb22015-11-09 16:58:28 -08002403 def VerifyOTA_Assertions(self):
2404 return self._DoCall("VerifyOTA_Assertions")
2405
Tao Bao76def242017-11-21 09:25:31 -08002406
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002407class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002408 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002409 self.name = name
2410 self.data = data
2411 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002412 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002413 self.sha1 = sha1(data).hexdigest()
2414
2415 @classmethod
2416 def FromLocalFile(cls, name, diskname):
2417 f = open(diskname, "rb")
2418 data = f.read()
2419 f.close()
2420 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002421
2422 def WriteToTemp(self):
2423 t = tempfile.NamedTemporaryFile()
2424 t.write(self.data)
2425 t.flush()
2426 return t
2427
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002428 def WriteToDir(self, d):
2429 with open(os.path.join(d, self.name), "wb") as fp:
2430 fp.write(self.data)
2431
Geremy Condra36bd3652014-02-06 19:45:10 -08002432 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002433 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002434
Tao Bao76def242017-11-21 09:25:31 -08002435
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002436DIFF_PROGRAM_BY_EXT = {
2437 ".gz" : "imgdiff",
2438 ".zip" : ["imgdiff", "-z"],
2439 ".jar" : ["imgdiff", "-z"],
2440 ".apk" : ["imgdiff", "-z"],
2441 ".img" : "imgdiff",
2442 }
2443
Tao Bao76def242017-11-21 09:25:31 -08002444
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002445class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002446 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002447 self.tf = tf
2448 self.sf = sf
2449 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002450 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002451
2452 def ComputePatch(self):
2453 """Compute the patch (as a string of data) needed to turn sf into
2454 tf. Returns the same tuple as GetPatch()."""
2455
2456 tf = self.tf
2457 sf = self.sf
2458
Doug Zongker24cd2802012-08-14 16:36:15 -07002459 if self.diff_program:
2460 diff_program = self.diff_program
2461 else:
2462 ext = os.path.splitext(tf.name)[1]
2463 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002464
2465 ttemp = tf.WriteToTemp()
2466 stemp = sf.WriteToTemp()
2467
2468 ext = os.path.splitext(tf.name)[1]
2469
2470 try:
2471 ptemp = tempfile.NamedTemporaryFile()
2472 if isinstance(diff_program, list):
2473 cmd = copy.copy(diff_program)
2474 else:
2475 cmd = [diff_program]
2476 cmd.append(stemp.name)
2477 cmd.append(ttemp.name)
2478 cmd.append(ptemp.name)
2479 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002480 err = []
2481 def run():
2482 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002483 if e:
2484 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002485 th = threading.Thread(target=run)
2486 th.start()
2487 th.join(timeout=300) # 5 mins
2488 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002489 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002490 p.terminate()
2491 th.join(5)
2492 if th.is_alive():
2493 p.kill()
2494 th.join()
2495
Tianjie Xua2a9f992018-01-05 15:15:54 -08002496 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002497 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002498 self.patch = None
2499 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002500 diff = ptemp.read()
2501 finally:
2502 ptemp.close()
2503 stemp.close()
2504 ttemp.close()
2505
2506 self.patch = diff
2507 return self.tf, self.sf, self.patch
2508
2509
2510 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002511 """Returns a tuple of (target_file, source_file, patch_data).
2512
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002513 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002514 computing the patch failed.
2515 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002516 return self.tf, self.sf, self.patch
2517
2518
2519def ComputeDifferences(diffs):
2520 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002521 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002522
2523 # Do the largest files first, to try and reduce the long-pole effect.
2524 by_size = [(i.tf.size, i) for i in diffs]
2525 by_size.sort(reverse=True)
2526 by_size = [i[1] for i in by_size]
2527
2528 lock = threading.Lock()
2529 diff_iter = iter(by_size) # accessed under lock
2530
2531 def worker():
2532 try:
2533 lock.acquire()
2534 for d in diff_iter:
2535 lock.release()
2536 start = time.time()
2537 d.ComputePatch()
2538 dur = time.time() - start
2539 lock.acquire()
2540
2541 tf, sf, patch = d.GetPatch()
2542 if sf.name == tf.name:
2543 name = tf.name
2544 else:
2545 name = "%s (%s)" % (tf.name, sf.name)
2546 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002547 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002548 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002549 logger.info(
2550 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2551 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002552 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002553 except Exception:
2554 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002555 raise
2556
2557 # start worker threads; wait for them all to finish.
2558 threads = [threading.Thread(target=worker)
2559 for i in range(OPTIONS.worker_threads)]
2560 for th in threads:
2561 th.start()
2562 while threads:
2563 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002564
2565
Dan Albert8b72aef2015-03-23 19:13:21 -07002566class BlockDifference(object):
2567 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002568 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002569 self.tgt = tgt
2570 self.src = src
2571 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002572 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002573 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002574
Tao Baodd2a5892015-03-12 12:32:37 -07002575 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002576 version = max(
2577 int(i) for i in
2578 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002579 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002580 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002581
Tianjie Xu41976c72019-07-03 13:57:01 -07002582 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2583 version=self.version,
2584 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002585 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002586 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002587 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002588 self.touched_src_ranges = b.touched_src_ranges
2589 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002590
Yifan Hong10c530d2018-12-27 17:34:18 -08002591 # On devices with dynamic partitions, for new partitions,
2592 # src is None but OPTIONS.source_info_dict is not.
2593 if OPTIONS.source_info_dict is None:
2594 is_dynamic_build = OPTIONS.info_dict.get(
2595 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002596 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002597 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002598 is_dynamic_build = OPTIONS.source_info_dict.get(
2599 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002600 is_dynamic_source = partition in shlex.split(
2601 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002602
Yifan Hongbb2658d2019-01-25 12:30:58 -08002603 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002604 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2605
Yifan Hongbb2658d2019-01-25 12:30:58 -08002606 # For dynamic partitions builds, check partition list in both source
2607 # and target build because new partitions may be added, and existing
2608 # partitions may be removed.
2609 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2610
Yifan Hong10c530d2018-12-27 17:34:18 -08002611 if is_dynamic:
2612 self.device = 'map_partition("%s")' % partition
2613 else:
2614 if OPTIONS.source_info_dict is None:
2615 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2616 else:
2617 _, device_path = GetTypeAndDevice("/" + partition,
2618 OPTIONS.source_info_dict)
2619 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002620
Tao Baod8d14be2016-02-04 14:26:02 -08002621 @property
2622 def required_cache(self):
2623 return self._required_cache
2624
Tao Bao76def242017-11-21 09:25:31 -08002625 def WriteScript(self, script, output_zip, progress=None,
2626 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002627 if not self.src:
2628 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002629 script.Print("Patching %s image unconditionally..." % (self.partition,))
2630 else:
2631 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002632
Dan Albert8b72aef2015-03-23 19:13:21 -07002633 if progress:
2634 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002635 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002636
2637 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002638 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002639
Tao Bao9bc6bb22015-11-09 16:58:28 -08002640 def WriteStrictVerifyScript(self, script):
2641 """Verify all the blocks in the care_map, including clobbered blocks.
2642
2643 This differs from the WriteVerifyScript() function: a) it prints different
2644 error messages; b) it doesn't allow half-way updated images to pass the
2645 verification."""
2646
2647 partition = self.partition
2648 script.Print("Verifying %s..." % (partition,))
2649 ranges = self.tgt.care_map
2650 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002651 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002652 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2653 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002654 self.device, ranges_str,
2655 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002656 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002657 script.AppendExtra("")
2658
Tao Baod522bdc2016-04-12 15:53:16 -07002659 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002660 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002661
2662 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002663 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002664 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002665
2666 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002667 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002668 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002669 ranges = self.touched_src_ranges
2670 expected_sha1 = self.touched_src_sha1
2671 else:
2672 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2673 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002674
2675 # No blocks to be checked, skipping.
2676 if not ranges:
2677 return
2678
Tao Bao5ece99d2015-05-12 11:42:31 -07002679 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002680 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002681 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002682 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2683 '"%s.patch.dat")) then' % (
2684 self.device, ranges_str, expected_sha1,
2685 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002686 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002687 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002688
Tianjie Xufc3422a2015-12-15 11:53:59 -08002689 if self.version >= 4:
2690
2691 # Bug: 21124327
2692 # When generating incrementals for the system and vendor partitions in
2693 # version 4 or newer, explicitly check the first block (which contains
2694 # the superblock) of the partition to see if it's what we expect. If
2695 # this check fails, give an explicit log message about the partition
2696 # having been remounted R/W (the most likely explanation).
2697 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002698 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002699
2700 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002701 if partition == "system":
2702 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2703 else:
2704 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002705 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002706 'ifelse (block_image_recover({device}, "{ranges}") && '
2707 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002708 'package_extract_file("{partition}.transfer.list"), '
2709 '"{partition}.new.dat", "{partition}.patch.dat"), '
2710 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002711 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002712 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002713 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002714
Tao Baodd2a5892015-03-12 12:32:37 -07002715 # Abort the OTA update. Note that the incremental OTA cannot be applied
2716 # even if it may match the checksum of the target partition.
2717 # a) If version < 3, operations like move and erase will make changes
2718 # unconditionally and damage the partition.
2719 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002720 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002721 if partition == "system":
2722 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2723 else:
2724 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2725 script.AppendExtra((
2726 'abort("E%d: %s partition has unexpected contents");\n'
2727 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002728
Yifan Hong10c530d2018-12-27 17:34:18 -08002729 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002730 partition = self.partition
2731 script.Print('Verifying the updated %s image...' % (partition,))
2732 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2733 ranges = self.tgt.care_map
2734 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002735 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002736 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002737 self.device, ranges_str,
2738 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002739
2740 # Bug: 20881595
2741 # Verify that extended blocks are really zeroed out.
2742 if self.tgt.extended:
2743 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002744 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002745 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002746 self.device, ranges_str,
2747 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002748 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002749 if partition == "system":
2750 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2751 else:
2752 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002753 script.AppendExtra(
2754 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002755 ' abort("E%d: %s partition has unexpected non-zero contents after '
2756 'OTA update");\n'
2757 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002758 else:
2759 script.Print('Verified the updated %s image.' % (partition,))
2760
Tianjie Xu209db462016-05-24 17:34:52 -07002761 if partition == "system":
2762 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2763 else:
2764 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2765
Tao Bao5fcaaef2015-06-01 13:40:49 -07002766 script.AppendExtra(
2767 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002768 ' abort("E%d: %s partition has unexpected contents after OTA '
2769 'update");\n'
2770 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002771
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002772 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002773 ZipWrite(output_zip,
2774 '{}.transfer.list'.format(self.path),
2775 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002776
Tao Bao76def242017-11-21 09:25:31 -08002777 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2778 # its size. Quailty 9 almost triples the compression time but doesn't
2779 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002780 # zip | brotli(quality 6) | brotli(quality 9)
2781 # compressed_size: 942M | 869M (~8% reduced) | 854M
2782 # compression_time: 75s | 265s | 719s
2783 # decompression_time: 15s | 25s | 25s
2784
2785 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002786 brotli_cmd = ['brotli', '--quality=6',
2787 '--output={}.new.dat.br'.format(self.path),
2788 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002789 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002790 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002791
2792 new_data_name = '{}.new.dat.br'.format(self.partition)
2793 ZipWrite(output_zip,
2794 '{}.new.dat.br'.format(self.path),
2795 new_data_name,
2796 compress_type=zipfile.ZIP_STORED)
2797 else:
2798 new_data_name = '{}.new.dat'.format(self.partition)
2799 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2800
Dan Albert8e0178d2015-01-27 15:53:15 -08002801 ZipWrite(output_zip,
2802 '{}.patch.dat'.format(self.path),
2803 '{}.patch.dat'.format(self.partition),
2804 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002805
Tianjie Xu209db462016-05-24 17:34:52 -07002806 if self.partition == "system":
2807 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2808 else:
2809 code = ErrorCode.VENDOR_UPDATE_FAILURE
2810
Yifan Hong10c530d2018-12-27 17:34:18 -08002811 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002812 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002813 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002814 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002815 device=self.device, partition=self.partition,
2816 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002817 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002818
Dan Albert8b72aef2015-03-23 19:13:21 -07002819 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002820 data = source.ReadRangeSet(ranges)
2821 ctx = sha1()
2822
2823 for p in data:
2824 ctx.update(p)
2825
2826 return ctx.hexdigest()
2827
Tao Baoe9b61912015-07-09 17:37:49 -07002828 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2829 """Return the hash value for all zero blocks."""
2830 zero_block = '\x00' * 4096
2831 ctx = sha1()
2832 for _ in range(num_blocks):
2833 ctx.update(zero_block)
2834
2835 return ctx.hexdigest()
2836
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002837
Tianjie Xu41976c72019-07-03 13:57:01 -07002838# Expose these two classes to support vendor-specific scripts
2839DataImage = images.DataImage
2840EmptyImage = images.EmptyImage
2841
Tao Bao76def242017-11-21 09:25:31 -08002842
Doug Zongker96a57e72010-09-26 14:57:41 -07002843# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002844PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002845 "ext4": "EMMC",
2846 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002847 "f2fs": "EMMC",
2848 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002849}
Doug Zongker96a57e72010-09-26 14:57:41 -07002850
Tao Bao76def242017-11-21 09:25:31 -08002851
Doug Zongker96a57e72010-09-26 14:57:41 -07002852def GetTypeAndDevice(mount_point, info):
2853 fstab = info["fstab"]
2854 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002855 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2856 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002857 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002858 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002859
2860
2861def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002862 """Parses and converts a PEM-encoded certificate into DER-encoded.
2863
2864 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2865
2866 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002867 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002868 """
2869 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002870 save = False
2871 for line in data.split("\n"):
2872 if "--END CERTIFICATE--" in line:
2873 break
2874 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002875 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002876 if "--BEGIN CERTIFICATE--" in line:
2877 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002878 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002879 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002880
Tao Bao04e1f012018-02-04 12:13:35 -08002881
2882def ExtractPublicKey(cert):
2883 """Extracts the public key (PEM-encoded) from the given certificate file.
2884
2885 Args:
2886 cert: The certificate filename.
2887
2888 Returns:
2889 The public key string.
2890
2891 Raises:
2892 AssertionError: On non-zero return from 'openssl'.
2893 """
2894 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2895 # While openssl 1.1 writes the key into the given filename followed by '-out',
2896 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2897 # stdout instead.
2898 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2899 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2900 pubkey, stderrdata = proc.communicate()
2901 assert proc.returncode == 0, \
2902 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2903 return pubkey
2904
2905
Tao Bao1ac886e2019-06-26 11:58:22 -07002906def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002907 """Extracts the AVB public key from the given public or private key.
2908
2909 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002910 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002911 key: The input key file, which should be PEM-encoded public or private key.
2912
2913 Returns:
2914 The path to the extracted AVB public key file.
2915 """
2916 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2917 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002918 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002919 return output
2920
2921
Doug Zongker412c02f2014-02-13 10:58:24 -08002922def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2923 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002924 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002925
Tao Bao6d5d6232018-03-09 17:04:42 -08002926 Most of the space in the boot and recovery images is just the kernel, which is
2927 identical for the two, so the resulting patch should be efficient. Add it to
2928 the output zip, along with a shell script that is run from init.rc on first
2929 boot to actually do the patching and install the new recovery image.
2930
2931 Args:
2932 input_dir: The top-level input directory of the target-files.zip.
2933 output_sink: The callback function that writes the result.
2934 recovery_img: File object for the recovery image.
2935 boot_img: File objects for the boot image.
2936 info_dict: A dict returned by common.LoadInfoDict() on the input
2937 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002938 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002939 if info_dict is None:
2940 info_dict = OPTIONS.info_dict
2941
Tao Bao6d5d6232018-03-09 17:04:42 -08002942 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002943 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2944
2945 if board_uses_vendorimage:
2946 # In this case, the output sink is rooted at VENDOR
2947 recovery_img_path = "etc/recovery.img"
2948 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2949 sh_dir = "bin"
2950 else:
2951 # In this case the output sink is rooted at SYSTEM
2952 recovery_img_path = "vendor/etc/recovery.img"
2953 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2954 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002955
Tao Baof2cffbd2015-07-22 12:33:18 -07002956 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002957 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002958
2959 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002960 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002961 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002962 # With system-root-image, boot and recovery images will have mismatching
2963 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2964 # to handle such a case.
2965 if system_root_image:
2966 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002967 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002968 assert not os.path.exists(path)
2969 else:
2970 diff_program = ["imgdiff"]
2971 if os.path.exists(path):
2972 diff_program.append("-b")
2973 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002974 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002975 else:
2976 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002977
2978 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2979 _, _, patch = d.ComputePatch()
2980 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002981
Dan Albertebb19aa2015-03-27 19:11:53 -07002982 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002983 # The following GetTypeAndDevice()s need to use the path in the target
2984 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002985 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2986 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2987 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002988 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002989
Tao Baof2cffbd2015-07-22 12:33:18 -07002990 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002991
2992 # Note that we use /vendor to refer to the recovery resources. This will
2993 # work for a separate vendor partition mounted at /vendor or a
2994 # /system/vendor subdirectory on the system partition, for which init will
2995 # create a symlink from /vendor to /system/vendor.
2996
2997 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002998if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2999 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003000 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003001 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3002 log -t recovery "Installing new recovery image: succeeded" || \\
3003 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003004else
3005 log -t recovery "Recovery image already installed"
3006fi
3007""" % {'type': recovery_type,
3008 'device': recovery_device,
3009 'sha1': recovery_img.sha1,
3010 'size': recovery_img.size}
3011 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003012 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003013if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3014 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003015 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003016 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3017 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3018 log -t recovery "Installing new recovery image: succeeded" || \\
3019 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003020else
3021 log -t recovery "Recovery image already installed"
3022fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003023""" % {'boot_size': boot_img.size,
3024 'boot_sha1': boot_img.sha1,
3025 'recovery_size': recovery_img.size,
3026 'recovery_sha1': recovery_img.sha1,
3027 'boot_type': boot_type,
3028 'boot_device': boot_device,
3029 'recovery_type': recovery_type,
3030 'recovery_device': recovery_device,
3031 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003032
Bill Peckhame868aec2019-09-17 17:06:47 -07003033 # The install script location moved from /system/etc to /system/bin in the L
3034 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3035 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003036
Tao Bao32fcdab2018-10-12 10:30:39 -07003037 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003038
Tao Baoda30cfa2017-12-01 16:19:46 -08003039 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003040
3041
3042class DynamicPartitionUpdate(object):
3043 def __init__(self, src_group=None, tgt_group=None, progress=None,
3044 block_difference=None):
3045 self.src_group = src_group
3046 self.tgt_group = tgt_group
3047 self.progress = progress
3048 self.block_difference = block_difference
3049
3050 @property
3051 def src_size(self):
3052 if not self.block_difference:
3053 return 0
3054 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3055
3056 @property
3057 def tgt_size(self):
3058 if not self.block_difference:
3059 return 0
3060 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3061
3062 @staticmethod
3063 def _GetSparseImageSize(img):
3064 if not img:
3065 return 0
3066 return img.blocksize * img.total_blocks
3067
3068
3069class DynamicGroupUpdate(object):
3070 def __init__(self, src_size=None, tgt_size=None):
3071 # None: group does not exist. 0: no size limits.
3072 self.src_size = src_size
3073 self.tgt_size = tgt_size
3074
3075
3076class DynamicPartitionsDifference(object):
3077 def __init__(self, info_dict, block_diffs, progress_dict=None,
3078 source_info_dict=None):
3079 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003080 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003081
3082 self._remove_all_before_apply = False
3083 if source_info_dict is None:
3084 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003085 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003086
Tao Baof1113e92019-06-18 12:10:14 -07003087 block_diff_dict = collections.OrderedDict(
3088 [(e.partition, e) for e in block_diffs])
3089
Yifan Hong10c530d2018-12-27 17:34:18 -08003090 assert len(block_diff_dict) == len(block_diffs), \
3091 "Duplicated BlockDifference object for {}".format(
3092 [partition for partition, count in
3093 collections.Counter(e.partition for e in block_diffs).items()
3094 if count > 1])
3095
Yifan Hong79997e52019-01-23 16:56:19 -08003096 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003097
3098 for p, block_diff in block_diff_dict.items():
3099 self._partition_updates[p] = DynamicPartitionUpdate()
3100 self._partition_updates[p].block_difference = block_diff
3101
3102 for p, progress in progress_dict.items():
3103 if p in self._partition_updates:
3104 self._partition_updates[p].progress = progress
3105
3106 tgt_groups = shlex.split(info_dict.get(
3107 "super_partition_groups", "").strip())
3108 src_groups = shlex.split(source_info_dict.get(
3109 "super_partition_groups", "").strip())
3110
3111 for g in tgt_groups:
3112 for p in shlex.split(info_dict.get(
3113 "super_%s_partition_list" % g, "").strip()):
3114 assert p in self._partition_updates, \
3115 "{} is in target super_{}_partition_list but no BlockDifference " \
3116 "object is provided.".format(p, g)
3117 self._partition_updates[p].tgt_group = g
3118
3119 for g in src_groups:
3120 for p in shlex.split(source_info_dict.get(
3121 "super_%s_partition_list" % g, "").strip()):
3122 assert p in self._partition_updates, \
3123 "{} is in source super_{}_partition_list but no BlockDifference " \
3124 "object is provided.".format(p, g)
3125 self._partition_updates[p].src_group = g
3126
Yifan Hong45433e42019-01-18 13:55:25 -08003127 target_dynamic_partitions = set(shlex.split(info_dict.get(
3128 "dynamic_partition_list", "").strip()))
3129 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3130 if u.tgt_size)
3131 assert block_diffs_with_target == target_dynamic_partitions, \
3132 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3133 list(target_dynamic_partitions), list(block_diffs_with_target))
3134
3135 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3136 "dynamic_partition_list", "").strip()))
3137 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3138 if u.src_size)
3139 assert block_diffs_with_source == source_dynamic_partitions, \
3140 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3141 list(source_dynamic_partitions), list(block_diffs_with_source))
3142
Yifan Hong10c530d2018-12-27 17:34:18 -08003143 if self._partition_updates:
3144 logger.info("Updating dynamic partitions %s",
3145 self._partition_updates.keys())
3146
Yifan Hong79997e52019-01-23 16:56:19 -08003147 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003148
3149 for g in tgt_groups:
3150 self._group_updates[g] = DynamicGroupUpdate()
3151 self._group_updates[g].tgt_size = int(info_dict.get(
3152 "super_%s_group_size" % g, "0").strip())
3153
3154 for g in src_groups:
3155 if g not in self._group_updates:
3156 self._group_updates[g] = DynamicGroupUpdate()
3157 self._group_updates[g].src_size = int(source_info_dict.get(
3158 "super_%s_group_size" % g, "0").strip())
3159
3160 self._Compute()
3161
3162 def WriteScript(self, script, output_zip, write_verify_script=False):
3163 script.Comment('--- Start patching dynamic partitions ---')
3164 for p, u in self._partition_updates.items():
3165 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3166 script.Comment('Patch partition %s' % p)
3167 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3168 write_verify_script=False)
3169
3170 op_list_path = MakeTempFile()
3171 with open(op_list_path, 'w') as f:
3172 for line in self._op_list:
3173 f.write('{}\n'.format(line))
3174
3175 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3176
3177 script.Comment('Update dynamic partition metadata')
3178 script.AppendExtra('assert(update_dynamic_partitions('
3179 'package_extract_file("dynamic_partitions_op_list")));')
3180
3181 if write_verify_script:
3182 for p, u in self._partition_updates.items():
3183 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3184 u.block_difference.WritePostInstallVerifyScript(script)
3185 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3186
3187 for p, u in self._partition_updates.items():
3188 if u.tgt_size and u.src_size <= u.tgt_size:
3189 script.Comment('Patch partition %s' % p)
3190 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3191 write_verify_script=write_verify_script)
3192 if write_verify_script:
3193 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3194
3195 script.Comment('--- End patching dynamic partitions ---')
3196
3197 def _Compute(self):
3198 self._op_list = list()
3199
3200 def append(line):
3201 self._op_list.append(line)
3202
3203 def comment(line):
3204 self._op_list.append("# %s" % line)
3205
3206 if self._remove_all_before_apply:
3207 comment('Remove all existing dynamic partitions and groups before '
3208 'applying full OTA')
3209 append('remove_all_groups')
3210
3211 for p, u in self._partition_updates.items():
3212 if u.src_group and not u.tgt_group:
3213 append('remove %s' % p)
3214
3215 for p, u in self._partition_updates.items():
3216 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3217 comment('Move partition %s from %s to default' % (p, u.src_group))
3218 append('move %s default' % p)
3219
3220 for p, u in self._partition_updates.items():
3221 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3222 comment('Shrink partition %s from %d to %d' %
3223 (p, u.src_size, u.tgt_size))
3224 append('resize %s %s' % (p, u.tgt_size))
3225
3226 for g, u in self._group_updates.items():
3227 if u.src_size is not None and u.tgt_size is None:
3228 append('remove_group %s' % g)
3229 if (u.src_size is not None and u.tgt_size is not None and
3230 u.src_size > u.tgt_size):
3231 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3232 append('resize_group %s %d' % (g, u.tgt_size))
3233
3234 for g, u in self._group_updates.items():
3235 if u.src_size is None and u.tgt_size is not None:
3236 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3237 append('add_group %s %d' % (g, u.tgt_size))
3238 if (u.src_size is not None and u.tgt_size is not None and
3239 u.src_size < u.tgt_size):
3240 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3241 append('resize_group %s %d' % (g, u.tgt_size))
3242
3243 for p, u in self._partition_updates.items():
3244 if u.tgt_group and not u.src_group:
3245 comment('Add partition %s to group %s' % (p, u.tgt_group))
3246 append('add %s %s' % (p, u.tgt_group))
3247
3248 for p, u in self._partition_updates.items():
3249 if u.tgt_size and u.src_size < u.tgt_size:
3250 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3251 append('resize %s %d' % (p, u.tgt_size))
3252
3253 for p, u in self._partition_updates.items():
3254 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3255 comment('Move partition %s from default to %s' %
3256 (p, u.tgt_group))
3257 append('move %s %s' % (p, u.tgt_group))