blob: 1abf5a5cc6c3c3c1255b36208e5a4effeca9f12e [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
Daniel Normand5fe8622020-01-08 17:01:11 -0800426 def GetPartitionBuildProp(self, prop, partition):
427 """Returns the inquired build property for the provided partition."""
428 # If provided a partition for this property, only look within that
429 # partition's build.prop.
430 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
431 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
432 else:
433 prop = prop.replace("ro.", "ro.{}.".format(partition))
Greg Kaiser60225452020-05-09 00:30:33 +0000434 try:
435 return self.info_dict.get("{}.build.prop".format(partition), {})[prop]
436 except KeyError:
437 raise ExternalError("couldn't find %s in %s.build.prop" %
438 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800439
Tao Bao1c320f82019-10-04 23:25:12 -0700440 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800441 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700442 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
443 return self._ResolveRoProductBuildProp(prop)
444
Greg Kaiser60225452020-05-09 00:30:33 +0000445 try:
446 return self.info_dict.get("build.prop", {})[prop]
447 except KeyError:
448 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700449
450 def _ResolveRoProductBuildProp(self, prop):
451 """Resolves the inquired ro.product.* build property"""
Greg Kaiser60225452020-05-09 00:30:33 +0000452 prop_val = self.info_dict.get("build.prop", {}).get(prop)
Tao Bao1c320f82019-10-04 23:25:12 -0700453 if prop_val:
454 return prop_val
455
Steven Laver8e2086e2020-04-27 16:26:31 -0700456 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Greg Kaiser60225452020-05-09 00:30:33 +0000457 source_order_val = self.info_dict.get("build.prop", {}).get(
458 "ro.product.property_source_order")
Tao Bao1c320f82019-10-04 23:25:12 -0700459 if source_order_val:
460 source_order = source_order_val.split(",")
461 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700462 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700463
464 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700465 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700466 raise ExternalError(
467 "Invalid ro.product.property_source_order '{}'".format(source_order))
468
Greg Kaiser60225452020-05-09 00:30:33 +0000469 for source in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700470 source_prop = prop.replace(
Greg Kaiser60225452020-05-09 00:30:33 +0000471 "ro.product", "ro.product.{}".format(source), 1)
472 prop_val = self.info_dict.get(
473 "{}.build.prop".format(source), {}).get(source_prop)
Tao Bao1c320f82019-10-04 23:25:12 -0700474 if prop_val:
475 return prop_val
476
477 raise ExternalError("couldn't resolve {}".format(prop))
478
Steven Laver8e2086e2020-04-27 16:26:31 -0700479 def _GetRoProductPropsDefaultSourceOrder(self):
480 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
481 # values of these properties for each Android release.
Greg Kaiser60225452020-05-09 00:30:33 +0000482 android_codename = self.info_dict.get("build.prop", {}).get(
483 "ro.build.version.codename")
Steven Laver8e2086e2020-04-27 16:26:31 -0700484 if android_codename == "REL":
Greg Kaiser60225452020-05-09 00:30:33 +0000485 android_version = self.info_dict.get("build.prop", {}).get(
486 "ro.build.version.release")
Steven Laver8e2086e2020-04-27 16:26:31 -0700487 if android_version == "10":
488 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
489 # NOTE: float() conversion of android_version will have rounding error.
490 # We are checking for "9" or less, and using "< 10" is well outside of
491 # possible floating point rounding.
492 try:
493 android_version_val = float(android_version)
494 except ValueError:
495 android_version_val = 0
496 if android_version_val < 10:
497 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
498 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
499
Tao Bao1c320f82019-10-04 23:25:12 -0700500 def GetOemProperty(self, key):
501 if self.oem_props is not None and key in self.oem_props:
502 return self.oem_dicts[0][key]
503 return self.GetBuildProp(key)
504
Daniel Normand5fe8622020-01-08 17:01:11 -0800505 def GetPartitionFingerprint(self, partition):
506 return self._partition_fingerprints.get(partition, None)
507
508 def CalculatePartitionFingerprint(self, partition):
509 try:
510 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
511 except ExternalError:
512 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
513 self.GetPartitionBuildProp("ro.product.brand", partition),
514 self.GetPartitionBuildProp("ro.product.name", partition),
515 self.GetPartitionBuildProp("ro.product.device", partition),
516 self.GetPartitionBuildProp("ro.build.version.release", partition),
517 self.GetPartitionBuildProp("ro.build.id", partition),
518 self.GetPartitionBuildProp("ro.build.version.incremental", partition),
519 self.GetPartitionBuildProp("ro.build.type", partition),
520 self.GetPartitionBuildProp("ro.build.tags", partition))
521
Tao Bao1c320f82019-10-04 23:25:12 -0700522 def CalculateFingerprint(self):
523 if self.oem_props is None:
524 try:
525 return self.GetBuildProp("ro.build.fingerprint")
526 except ExternalError:
527 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
528 self.GetBuildProp("ro.product.brand"),
529 self.GetBuildProp("ro.product.name"),
530 self.GetBuildProp("ro.product.device"),
531 self.GetBuildProp("ro.build.version.release"),
532 self.GetBuildProp("ro.build.id"),
533 self.GetBuildProp("ro.build.version.incremental"),
534 self.GetBuildProp("ro.build.type"),
535 self.GetBuildProp("ro.build.tags"))
536 return "%s/%s/%s:%s" % (
537 self.GetOemProperty("ro.product.brand"),
538 self.GetOemProperty("ro.product.name"),
539 self.GetOemProperty("ro.product.device"),
540 self.GetBuildProp("ro.build.thumbprint"))
541
542 def WriteMountOemScript(self, script):
543 assert self.oem_props is not None
544 recovery_mount_options = self.info_dict.get("recovery_mount_options")
545 script.Mount("/oem", recovery_mount_options)
546
547 def WriteDeviceAssertions(self, script, oem_no_mount):
548 # Read the property directly if not using OEM properties.
549 if not self.oem_props:
550 script.AssertDevice(self.device)
551 return
552
553 # Otherwise assert OEM properties.
554 if not self.oem_dicts:
555 raise ExternalError(
556 "No OEM file provided to answer expected assertions")
557
558 for prop in self.oem_props.split():
559 values = []
560 for oem_dict in self.oem_dicts:
561 if prop in oem_dict:
562 values.append(oem_dict[prop])
563 if not values:
564 raise ExternalError(
565 "The OEM file is missing the property %s" % (prop,))
566 script.AssertOemProperty(prop, values, oem_no_mount)
567
568
Tao Bao410ad8b2018-08-24 12:08:38 -0700569def LoadInfoDict(input_file, repacking=False):
570 """Loads the key/value pairs from the given input target_files.
571
572 It reads `META/misc_info.txt` file in the target_files input, does sanity
573 checks and returns the parsed key/value pairs for to the given build. It's
574 usually called early when working on input target_files files, e.g. when
575 generating OTAs, or signing builds. Note that the function may be called
576 against an old target_files file (i.e. from past dessert releases). So the
577 property parsing needs to be backward compatible.
578
579 In a `META/misc_info.txt`, a few properties are stored as links to the files
580 in the PRODUCT_OUT directory. It works fine with the build system. However,
581 they are no longer available when (re)generating images from target_files zip.
582 When `repacking` is True, redirect these properties to the actual files in the
583 unzipped directory.
584
585 Args:
586 input_file: The input target_files file, which could be an open
587 zipfile.ZipFile instance, or a str for the dir that contains the files
588 unzipped from a target_files file.
589 repacking: Whether it's trying repack an target_files file after loading the
590 info dict (default: False). If so, it will rewrite a few loaded
591 properties (e.g. selinux_fc, root_dir) to point to the actual files in
592 target_files file. When doing repacking, `input_file` must be a dir.
593
594 Returns:
595 A dict that contains the parsed key/value pairs.
596
597 Raises:
598 AssertionError: On invalid input arguments.
599 ValueError: On malformed input values.
600 """
601 if repacking:
602 assert isinstance(input_file, str), \
603 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700604
Doug Zongkerc9253822014-02-04 12:17:58 -0800605 def read_helper(fn):
Greg Kaiser60225452020-05-09 00:30:33 +0000606 if isinstance(input_file, zipfile.ZipFile):
607 return input_file.read(fn).decode()
608 else:
609 path = os.path.join(input_file, *fn.split("/"))
610 try:
611 with open(path) as f:
612 return f.read()
613 except IOError as e:
614 if e.errno == errno.ENOENT:
615 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800616
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700617 try:
Michael Runge6e836112014-04-15 17:40:21 -0700618 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700619 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700620 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700621
Tao Bao410ad8b2018-08-24 12:08:38 -0700622 if "recovery_api_version" not in d:
623 raise ValueError("Failed to find 'recovery_api_version'")
624 if "fstab_version" not in d:
625 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800626
Tao Bao410ad8b2018-08-24 12:08:38 -0700627 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700628 # "selinux_fc" properties should point to the file_contexts files
629 # (file_contexts.bin) under META/.
630 for key in d:
631 if key.endswith("selinux_fc"):
632 fc_basename = os.path.basename(d[key])
633 fc_config = os.path.join(input_file, "META", fc_basename)
634 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700635
Daniel Norman72c626f2019-05-13 15:58:14 -0700636 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700637
Tom Cherryd14b8952018-08-09 14:26:00 -0700638 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700639 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700640 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700641 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700642
David Anderson0ec64ac2019-12-06 12:21:18 -0800643 # Redirect {partition}_base_fs_file for each of the named partitions.
644 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
645 key_name = part_name + "_base_fs_file"
646 if key_name not in d:
647 continue
648 basename = os.path.basename(d[key_name])
649 base_fs_file = os.path.join(input_file, "META", basename)
650 if os.path.exists(base_fs_file):
651 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700652 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700653 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800654 "Failed to find %s base fs file: %s", part_name, base_fs_file)
655 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700656
Doug Zongker37974732010-09-16 17:44:38 -0700657 def makeint(key):
658 if key in d:
659 d[key] = int(d[key], 0)
660
661 makeint("recovery_api_version")
662 makeint("blocksize")
663 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700664 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700665 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700666 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700667 makeint("recovery_size")
668 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800669 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700670
Tao Bao765668f2019-10-04 22:03:00 -0700671 # Load recovery fstab if applicable.
672 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800673
Tianjie Xu861f4132018-09-12 11:49:33 -0700674 # Tries to load the build props for all partitions with care_map, including
675 # system and vendor.
676 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800677 partition_prop = "{}.build.prop".format(partition)
Greg Kaiser60225452020-05-09 00:30:33 +0000678 d[partition_prop] = LoadBuildProp(
679 read_helper, "{}/build.prop".format(partition.upper()))
680 # Some partition might use /<partition>/etc/build.prop as the new path.
681 # TODO: try new path first when majority of them switch to the new path.
682 if not d[partition_prop]:
683 d[partition_prop] = LoadBuildProp(
684 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700685 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800686
Tao Bao3ed35d32019-10-07 20:48:48 -0700687 # Set up the salt (based on fingerprint) that will be used when adding AVB
688 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800689 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700690 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800691 for partition in PARTITIONS_WITH_CARE_MAP:
692 fingerprint = build_info.GetPartitionFingerprint(partition)
693 if fingerprint:
694 d["avb_{}_salt".format(partition)] = sha256(fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800695
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700696 return d
697
Tao Baod1de6f32017-03-01 16:38:48 -0800698
Greg Kaiser60225452020-05-09 00:30:33 +0000699def LoadBuildProp(read_helper, prop_file):
700 try:
701 data = read_helper(prop_file)
702 except KeyError:
703 logger.warning("Failed to read %s", prop_file)
704 data = ""
705 return LoadDictionaryFromLines(data.split("\n"))
706
707
Daniel Norman4cc9df62019-07-18 10:11:07 -0700708def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900709 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700710 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900711
Daniel Norman4cc9df62019-07-18 10:11:07 -0700712
713def LoadDictionaryFromFile(file_path):
714 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900715 return LoadDictionaryFromLines(lines)
716
717
Michael Runge6e836112014-04-15 17:40:21 -0700718def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700719 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700720 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700721 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700722 if not line or line.startswith("#"):
723 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700724 if "=" in line:
725 name, value = line.split("=", 1)
726 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700727 return d
728
Tao Baod1de6f32017-03-01 16:38:48 -0800729
Tianjie Xucfa86222016-03-07 16:31:19 -0800730def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
731 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700732 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800733 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700734 self.mount_point = mount_point
735 self.fs_type = fs_type
736 self.device = device
737 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700738 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700739
740 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800741 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700742 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700743 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700744 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700745
Tao Baod1de6f32017-03-01 16:38:48 -0800746 assert fstab_version == 2
747
748 d = {}
749 for line in data.split("\n"):
750 line = line.strip()
751 if not line or line.startswith("#"):
752 continue
753
754 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
755 pieces = line.split()
756 if len(pieces) != 5:
757 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
758
759 # Ignore entries that are managed by vold.
760 options = pieces[4]
761 if "voldmanaged=" in options:
762 continue
763
764 # It's a good line, parse it.
765 length = 0
766 options = options.split(",")
767 for i in options:
768 if i.startswith("length="):
769 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800770 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800771 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700772 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800773
Tao Baod1de6f32017-03-01 16:38:48 -0800774 mount_flags = pieces[3]
775 # Honor the SELinux context if present.
776 context = None
777 for i in mount_flags.split(","):
778 if i.startswith("context="):
779 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800780
Tao Baod1de6f32017-03-01 16:38:48 -0800781 mount_point = pieces[1]
782 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
783 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800784
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700785 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700786 # system. Other areas assume system is always at "/system" so point /system
787 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700788 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800789 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700790 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700791 return d
792
793
Tao Bao765668f2019-10-04 22:03:00 -0700794def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
795 """Finds the path to recovery fstab and loads its contents."""
796 # recovery fstab is only meaningful when installing an update via recovery
797 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
798 if info_dict.get('ab_update') == 'true':
799 return None
800
801 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
802 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
803 # cases, since it may load the info_dict from an old build (e.g. when
804 # generating incremental OTAs from that build).
805 system_root_image = info_dict.get('system_root_image') == 'true'
806 if info_dict.get('no_recovery') != 'true':
807 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
808 if isinstance(input_file, zipfile.ZipFile):
809 if recovery_fstab_path not in input_file.namelist():
810 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
811 else:
812 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
813 if not os.path.exists(path):
814 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
815 return LoadRecoveryFSTab(
816 read_helper, info_dict['fstab_version'], recovery_fstab_path,
817 system_root_image)
818
819 if info_dict.get('recovery_as_boot') == 'true':
820 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
821 if isinstance(input_file, zipfile.ZipFile):
822 if recovery_fstab_path not in input_file.namelist():
823 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
824 else:
825 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
826 if not os.path.exists(path):
827 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
828 return LoadRecoveryFSTab(
829 read_helper, info_dict['fstab_version'], recovery_fstab_path,
830 system_root_image)
831
832 return None
833
834
Doug Zongker37974732010-09-16 17:44:38 -0700835def DumpInfoDict(d):
836 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700837 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700838
Dan Albert8b72aef2015-03-23 19:13:21 -0700839
Daniel Norman55417142019-11-25 16:04:36 -0800840def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700841 """Merges dynamic partition info variables.
842
843 Args:
844 framework_dict: The dictionary of dynamic partition info variables from the
845 partial framework target files.
846 vendor_dict: The dictionary of dynamic partition info variables from the
847 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700848
849 Returns:
850 The merged dynamic partition info dictionary.
851 """
852 merged_dict = {}
853 # Partition groups and group sizes are defined by the vendor dict because
854 # these values may vary for each board that uses a shared system image.
855 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800856 framework_dynamic_partition_list = framework_dict.get(
857 "dynamic_partition_list", "")
858 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
859 merged_dict["dynamic_partition_list"] = ("%s %s" % (
860 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700861 for partition_group in merged_dict["super_partition_groups"].split(" "):
862 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800863 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700864 if key not in vendor_dict:
865 raise ValueError("Vendor dict does not contain required key %s." % key)
866 merged_dict[key] = vendor_dict[key]
867
868 # Set the partition group's partition list using a concatenation of the
869 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800870 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700871 merged_dict[key] = (
872 "%s %s" %
873 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530874
875 # Pick virtual ab related flags from vendor dict, if defined.
876 if "virtual_ab" in vendor_dict.keys():
877 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
878 if "virtual_ab_retrofit" in vendor_dict.keys():
879 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700880 return merged_dict
881
882
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800883def AppendAVBSigningArgs(cmd, partition):
884 """Append signing arguments for avbtool."""
885 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
886 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700887 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
888 new_key_path = os.path.join(OPTIONS.search_path, key_path)
889 if os.path.exists(new_key_path):
890 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800891 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
892 if key_path and algorithm:
893 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700894 avb_salt = OPTIONS.info_dict.get("avb_salt")
895 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700896 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700897 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800898
899
Tao Bao765668f2019-10-04 22:03:00 -0700900def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700901 """Returns the VBMeta arguments for partition.
902
903 It sets up the VBMeta argument by including the partition descriptor from the
904 given 'image', or by configuring the partition as a chained partition.
905
906 Args:
907 partition: The name of the partition (e.g. "system").
908 image: The path to the partition image.
909 info_dict: A dict returned by common.LoadInfoDict(). Will use
910 OPTIONS.info_dict if None has been given.
911
912 Returns:
913 A list of VBMeta arguments.
914 """
915 if info_dict is None:
916 info_dict = OPTIONS.info_dict
917
918 # Check if chain partition is used.
919 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +0800920 if not key_path:
921 return ["--include_descriptors_from_image", image]
922
923 # For a non-A/B device, we don't chain /recovery nor include its descriptor
924 # into vbmeta.img. The recovery image will be configured on an independent
925 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
926 # See details at
927 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -0700928 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +0800929 return []
930
931 # Otherwise chain the partition into vbmeta.
932 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
933 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -0700934
935
Tao Bao02a08592018-07-22 12:40:45 -0700936def GetAvbChainedPartitionArg(partition, info_dict, key=None):
937 """Constructs and returns the arg to build or verify a chained partition.
938
939 Args:
940 partition: The partition name.
941 info_dict: The info dict to look up the key info and rollback index
942 location.
943 key: The key to be used for building or verifying the partition. Defaults to
944 the key listed in info_dict.
945
946 Returns:
947 A string of form "partition:rollback_index_location:key" that can be used to
948 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700949 """
950 if key is None:
951 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700952 if key and not os.path.exists(key) and OPTIONS.search_path:
953 new_key_path = os.path.join(OPTIONS.search_path, key)
954 if os.path.exists(new_key_path):
955 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700956 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700957 rollback_index_location = info_dict[
958 "avb_" + partition + "_rollback_index_location"]
959 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
960
961
Tianjie20dd8f22020-04-19 15:51:16 -0700962def ConstructAftlMakeImageCommands(output_image):
963 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -0700964
965 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -0700966 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -0700967 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
968 assert OPTIONS.aftl_manufacturer_key_path is not None, \
969 'No AFTL manufacturer key provided.'
970
971 vbmeta_image = MakeTempFile()
972 os.rename(output_image, vbmeta_image)
973 build_info = BuildInfo(OPTIONS.info_dict)
974 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -0700975 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -0700976 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -0700977 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -0700978 "--vbmeta_image_path", vbmeta_image,
979 "--output", output_image,
980 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -0700981 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -0700982 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
983 "--algorithm", "SHA256_RSA4096",
984 "--padding", "4096"]
985 if OPTIONS.aftl_signer_helper:
986 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -0700987 return aftl_cmd
988
989
990def AddAftlInclusionProof(output_image):
991 """Appends the aftl inclusion proof to the vbmeta image."""
992
993 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -0700994 RunAndCheckOutput(aftl_cmd)
995
996 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
997 output_image, '--transparency_log_pub_keys',
998 OPTIONS.aftl_key_path]
999 RunAndCheckOutput(verify_cmd)
1000
1001
Daniel Norman276f0622019-07-26 14:13:51 -07001002def BuildVBMeta(image_path, partitions, name, needed_partitions):
1003 """Creates a VBMeta image.
1004
1005 It generates the requested VBMeta image. The requested image could be for
1006 top-level or chained VBMeta image, which is determined based on the name.
1007
1008 Args:
1009 image_path: The output path for the new VBMeta image.
1010 partitions: A dict that's keyed by partition names with image paths as
1011 values. Only valid partition names are accepted, as listed in
1012 common.AVB_PARTITIONS.
1013 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1014 needed_partitions: Partitions whose descriptors should be included into the
1015 generated VBMeta image.
1016
1017 Raises:
1018 AssertionError: On invalid input args.
1019 """
1020 avbtool = OPTIONS.info_dict["avb_avbtool"]
1021 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1022 AppendAVBSigningArgs(cmd, name)
1023
1024 for partition, path in partitions.items():
1025 if partition not in needed_partitions:
1026 continue
1027 assert (partition in AVB_PARTITIONS or
1028 partition in AVB_VBMETA_PARTITIONS), \
1029 'Unknown partition: {}'.format(partition)
1030 assert os.path.exists(path), \
1031 'Failed to find {} for {}'.format(path, partition)
1032 cmd.extend(GetAvbPartitionArg(partition, path))
1033
1034 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1035 if args and args.strip():
1036 split_args = shlex.split(args)
1037 for index, arg in enumerate(split_args[:-1]):
1038 # Sanity check that the image file exists. Some images might be defined
1039 # as a path relative to source tree, which may not be available at the
1040 # same location when running this script (we have the input target_files
1041 # zip only). For such cases, we additionally scan other locations (e.g.
1042 # IMAGES/, RADIO/, etc) before bailing out.
1043 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001044 chained_image = split_args[index + 1]
1045 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001046 continue
1047 found = False
1048 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1049 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001050 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001051 if os.path.exists(alt_path):
1052 split_args[index + 1] = alt_path
1053 found = True
1054 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001055 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001056 cmd.extend(split_args)
1057
1058 RunAndCheckOutput(cmd)
1059
Tianjie Xueaed60c2020-03-12 00:33:28 -07001060 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001061 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001062 AddAftlInclusionProof(image_path)
1063
Daniel Norman276f0622019-07-26 14:13:51 -07001064
Steve Mucklee1b10862019-07-10 10:49:37 -07001065def _MakeRamdisk(sourcedir, fs_config_file=None):
1066 ramdisk_img = tempfile.NamedTemporaryFile()
1067
1068 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1069 cmd = ["mkbootfs", "-f", fs_config_file,
1070 os.path.join(sourcedir, "RAMDISK")]
1071 else:
1072 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1073 p1 = Run(cmd, stdout=subprocess.PIPE)
1074 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1075
1076 p2.wait()
1077 p1.wait()
1078 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1079 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1080
1081 return ramdisk_img
1082
1083
Steve Muckle9793cf62020-04-08 18:27:00 -07001084def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001085 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001086 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001087
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001088 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001089 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1090 we are building a two-step special image (i.e. building a recovery image to
1091 be loaded into /boot in two-step OTAs).
1092
1093 Return the image data, or None if sourcedir does not appear to contains files
1094 for building the requested image.
1095 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001096
Steve Muckle9793cf62020-04-08 18:27:00 -07001097 # "boot" or "recovery", without extension.
1098 partition_name = os.path.basename(sourcedir).lower()
1099
1100 if partition_name == "recovery":
1101 kernel = "kernel"
1102 else:
1103 kernel = image_name.replace("boot", "kernel")
1104 kernel = kernel.replace(".img","")
1105 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001106 return None
1107
1108 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001109 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001110
Doug Zongkerd5131602012-08-02 14:46:42 -07001111 if info_dict is None:
1112 info_dict = OPTIONS.info_dict
1113
Doug Zongkereef39442009-04-02 12:14:19 -07001114 img = tempfile.NamedTemporaryFile()
1115
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001116 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001117 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001118
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001119 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1120 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1121
Steve Muckle9793cf62020-04-08 18:27:00 -07001122 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001123
Benoit Fradina45a8682014-07-14 21:00:43 +02001124 fn = os.path.join(sourcedir, "second")
1125 if os.access(fn, os.F_OK):
1126 cmd.append("--second")
1127 cmd.append(fn)
1128
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001129 fn = os.path.join(sourcedir, "dtb")
1130 if os.access(fn, os.F_OK):
1131 cmd.append("--dtb")
1132 cmd.append(fn)
1133
Doug Zongker171f1cd2009-06-15 22:36:37 -07001134 fn = os.path.join(sourcedir, "cmdline")
1135 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001136 cmd.append("--cmdline")
1137 cmd.append(open(fn).read().rstrip("\n"))
1138
1139 fn = os.path.join(sourcedir, "base")
1140 if os.access(fn, os.F_OK):
1141 cmd.append("--base")
1142 cmd.append(open(fn).read().rstrip("\n"))
1143
Ying Wang4de6b5b2010-08-25 14:29:34 -07001144 fn = os.path.join(sourcedir, "pagesize")
1145 if os.access(fn, os.F_OK):
1146 cmd.append("--pagesize")
1147 cmd.append(open(fn).read().rstrip("\n"))
1148
Steve Mucklef84668e2020-03-16 19:13:46 -07001149 if partition_name == "recovery":
1150 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301151 if not args:
1152 # Fall back to "mkbootimg_args" for recovery image
1153 # in case "recovery_mkbootimg_args" is not set.
1154 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001155 else:
1156 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001157 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001158 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001159
Tao Bao76def242017-11-21 09:25:31 -08001160 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001161 if args and args.strip():
1162 cmd.extend(shlex.split(args))
1163
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001164 if has_ramdisk:
1165 cmd.extend(["--ramdisk", ramdisk_img.name])
1166
Tao Baod95e9fd2015-03-29 23:07:41 -07001167 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001168 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001169 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001170 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001171 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001172 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001173
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001174 if partition_name == "recovery":
1175 if info_dict.get("include_recovery_dtbo") == "true":
1176 fn = os.path.join(sourcedir, "recovery_dtbo")
1177 cmd.extend(["--recovery_dtbo", fn])
1178 if info_dict.get("include_recovery_acpio") == "true":
1179 fn = os.path.join(sourcedir, "recovery_acpio")
1180 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001181
Tao Bao986ee862018-10-04 15:46:16 -07001182 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001183
Tao Bao76def242017-11-21 09:25:31 -08001184 if (info_dict.get("boot_signer") == "true" and
1185 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001186 # Hard-code the path as "/boot" for two-step special recovery image (which
1187 # will be loaded into /boot during the two-step OTA).
1188 if two_step_image:
1189 path = "/boot"
1190 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001191 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001192 cmd = [OPTIONS.boot_signer_path]
1193 cmd.extend(OPTIONS.boot_signer_args)
1194 cmd.extend([path, img.name,
1195 info_dict["verity_key"] + ".pk8",
1196 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001197 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001198
Tao Baod95e9fd2015-03-29 23:07:41 -07001199 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001200 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001201 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001202 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001203 # We have switched from the prebuilt futility binary to using the tool
1204 # (futility-host) built from the source. Override the setting in the old
1205 # TF.zip.
1206 futility = info_dict["futility"]
1207 if futility.startswith("prebuilts/"):
1208 futility = "futility-host"
1209 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001210 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001211 info_dict["vboot_key"] + ".vbprivk",
1212 info_dict["vboot_subkey"] + ".vbprivk",
1213 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001214 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001215 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001216
Tao Baof3282b42015-04-01 11:21:55 -07001217 # Clean up the temp files.
1218 img_unsigned.close()
1219 img_keyblock.close()
1220
David Zeuthen8fecb282017-12-01 16:24:01 -05001221 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001222 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001223 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001224 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001225 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001226 "--partition_size", str(part_size), "--partition_name",
1227 partition_name]
1228 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001229 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001230 if args and args.strip():
1231 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001232 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001233
1234 img.seek(os.SEEK_SET, 0)
1235 data = img.read()
1236
1237 if has_ramdisk:
1238 ramdisk_img.close()
1239 img.close()
1240
1241 return data
1242
1243
Doug Zongkerd5131602012-08-02 14:46:42 -07001244def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001245 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001246 """Return a File object with the desired bootable image.
1247
1248 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1249 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1250 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001251
Doug Zongker55d93282011-01-25 17:03:34 -08001252 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1253 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001254 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001255 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001256
1257 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1258 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001259 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001260 return File.FromLocalFile(name, prebuilt_path)
1261
Tao Bao32fcdab2018-10-12 10:30:39 -07001262 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001263
1264 if info_dict is None:
1265 info_dict = OPTIONS.info_dict
1266
1267 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001268 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1269 # for recovery.
1270 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1271 prebuilt_name != "boot.img" or
1272 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001273
Doug Zongker6f1d0312014-08-22 08:07:12 -07001274 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001275 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001276 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001277 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001278 if data:
1279 return File(name, data)
1280 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001281
Doug Zongkereef39442009-04-02 12:14:19 -07001282
Steve Mucklee1b10862019-07-10 10:49:37 -07001283def _BuildVendorBootImage(sourcedir, info_dict=None):
1284 """Build a vendor boot image from the specified sourcedir.
1285
1286 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1287 turn them into a vendor boot image.
1288
1289 Return the image data, or None if sourcedir does not appear to contains files
1290 for building the requested image.
1291 """
1292
1293 if info_dict is None:
1294 info_dict = OPTIONS.info_dict
1295
1296 img = tempfile.NamedTemporaryFile()
1297
1298 ramdisk_img = _MakeRamdisk(sourcedir)
1299
1300 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1301 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1302
1303 cmd = [mkbootimg]
1304
1305 fn = os.path.join(sourcedir, "dtb")
1306 if os.access(fn, os.F_OK):
1307 cmd.append("--dtb")
1308 cmd.append(fn)
1309
1310 fn = os.path.join(sourcedir, "vendor_cmdline")
1311 if os.access(fn, os.F_OK):
1312 cmd.append("--vendor_cmdline")
1313 cmd.append(open(fn).read().rstrip("\n"))
1314
1315 fn = os.path.join(sourcedir, "base")
1316 if os.access(fn, os.F_OK):
1317 cmd.append("--base")
1318 cmd.append(open(fn).read().rstrip("\n"))
1319
1320 fn = os.path.join(sourcedir, "pagesize")
1321 if os.access(fn, os.F_OK):
1322 cmd.append("--pagesize")
1323 cmd.append(open(fn).read().rstrip("\n"))
1324
1325 args = info_dict.get("mkbootimg_args")
1326 if args and args.strip():
1327 cmd.extend(shlex.split(args))
1328
1329 args = info_dict.get("mkbootimg_version_args")
1330 if args and args.strip():
1331 cmd.extend(shlex.split(args))
1332
1333 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1334 cmd.extend(["--vendor_boot", img.name])
1335
1336 RunAndCheckOutput(cmd)
1337
1338 # AVB: if enabled, calculate and add hash.
1339 if info_dict.get("avb_enable") == "true":
1340 avbtool = info_dict["avb_avbtool"]
1341 part_size = info_dict["vendor_boot_size"]
1342 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001343 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001344 AppendAVBSigningArgs(cmd, "vendor_boot")
1345 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1346 if args and args.strip():
1347 cmd.extend(shlex.split(args))
1348 RunAndCheckOutput(cmd)
1349
1350 img.seek(os.SEEK_SET, 0)
1351 data = img.read()
1352
1353 ramdisk_img.close()
1354 img.close()
1355
1356 return data
1357
1358
1359def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1360 info_dict=None):
1361 """Return a File object with the desired vendor boot image.
1362
1363 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1364 the source files in 'unpack_dir'/'tree_subdir'."""
1365
1366 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1367 if os.path.exists(prebuilt_path):
1368 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1369 return File.FromLocalFile(name, prebuilt_path)
1370
1371 logger.info("building image from target_files %s...", tree_subdir)
1372
1373 if info_dict is None:
1374 info_dict = OPTIONS.info_dict
1375
1376 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1377 if data:
1378 return File(name, data)
1379 return None
1380
1381
Narayan Kamatha07bf042017-08-14 14:49:21 +01001382def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001383 """Gunzips the given gzip compressed file to a given output file."""
1384 with gzip.open(in_filename, "rb") as in_file, \
1385 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001386 shutil.copyfileobj(in_file, out_file)
1387
1388
Tao Bao0ff15de2019-03-20 11:26:06 -07001389def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001390 """Unzips the archive to the given directory.
1391
1392 Args:
1393 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001394 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001395 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1396 archvie. Non-matching patterns will be filtered out. If there's no match
1397 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001398 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001399 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001400 if patterns is not None:
1401 # Filter out non-matching patterns. unzip will complain otherwise.
1402 with zipfile.ZipFile(filename) as input_zip:
1403 names = input_zip.namelist()
1404 filtered = [
1405 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1406
1407 # There isn't any matching files. Don't unzip anything.
1408 if not filtered:
1409 return
1410 cmd.extend(filtered)
1411
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001412 RunAndCheckOutput(cmd)
1413
1414
Doug Zongker75f17362009-12-08 13:46:44 -08001415def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001416 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001417
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001418 Args:
1419 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1420 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1421
1422 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1423 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001424
Tao Bao1c830bf2017-12-25 10:43:47 -08001425 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001426 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001427 """
Doug Zongkereef39442009-04-02 12:14:19 -07001428
Tao Bao1c830bf2017-12-25 10:43:47 -08001429 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001430 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1431 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001432 UnzipToDir(m.group(1), tmp, pattern)
1433 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001434 filename = m.group(1)
1435 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001436 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001437
Tao Baodba59ee2018-01-09 13:21:02 -08001438 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001439
1440
Yifan Hong8a66a712019-04-04 15:37:57 -07001441def GetUserImage(which, tmpdir, input_zip,
1442 info_dict=None,
1443 allow_shared_blocks=None,
1444 hashtree_info_generator=None,
1445 reset_file_map=False):
1446 """Returns an Image object suitable for passing to BlockImageDiff.
1447
1448 This function loads the specified image from the given path. If the specified
1449 image is sparse, it also performs additional processing for OTA purpose. For
1450 example, it always adds block 0 to clobbered blocks list. It also detects
1451 files that cannot be reconstructed from the block list, for whom we should
1452 avoid applying imgdiff.
1453
1454 Args:
1455 which: The partition name.
1456 tmpdir: The directory that contains the prebuilt image and block map file.
1457 input_zip: The target-files ZIP archive.
1458 info_dict: The dict to be looked up for relevant info.
1459 allow_shared_blocks: If image is sparse, whether having shared blocks is
1460 allowed. If none, it is looked up from info_dict.
1461 hashtree_info_generator: If present and image is sparse, generates the
1462 hashtree_info for this sparse image.
1463 reset_file_map: If true and image is sparse, reset file map before returning
1464 the image.
1465 Returns:
1466 A Image object. If it is a sparse image and reset_file_map is False, the
1467 image will have file_map info loaded.
1468 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001469 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001470 info_dict = LoadInfoDict(input_zip)
1471
1472 is_sparse = info_dict.get("extfs_sparse_flag")
1473
1474 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1475 # shared blocks (i.e. some blocks will show up in multiple files' block
1476 # list). We can only allocate such shared blocks to the first "owner", and
1477 # disable imgdiff for all later occurrences.
1478 if allow_shared_blocks is None:
1479 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1480
1481 if is_sparse:
1482 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1483 hashtree_info_generator)
1484 if reset_file_map:
1485 img.ResetFileMap()
1486 return img
1487 else:
1488 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1489
1490
1491def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1492 """Returns a Image object suitable for passing to BlockImageDiff.
1493
1494 This function loads the specified non-sparse image from the given path.
1495
1496 Args:
1497 which: The partition name.
1498 tmpdir: The directory that contains the prebuilt image and block map file.
1499 Returns:
1500 A Image object.
1501 """
1502 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1503 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1504
1505 # The image and map files must have been created prior to calling
1506 # ota_from_target_files.py (since LMP).
1507 assert os.path.exists(path) and os.path.exists(mappath)
1508
Tianjie Xu41976c72019-07-03 13:57:01 -07001509 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1510
Yifan Hong8a66a712019-04-04 15:37:57 -07001511
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001512def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1513 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001514 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1515
1516 This function loads the specified sparse image from the given path, and
1517 performs additional processing for OTA purpose. For example, it always adds
1518 block 0 to clobbered blocks list. It also detects files that cannot be
1519 reconstructed from the block list, for whom we should avoid applying imgdiff.
1520
1521 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001522 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001523 tmpdir: The directory that contains the prebuilt image and block map file.
1524 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001525 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001526 hashtree_info_generator: If present, generates the hashtree_info for this
1527 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001528 Returns:
1529 A SparseImage object, with file_map info loaded.
1530 """
Tao Baoc765cca2018-01-31 17:32:40 -08001531 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1532 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1533
1534 # The image and map files must have been created prior to calling
1535 # ota_from_target_files.py (since LMP).
1536 assert os.path.exists(path) and os.path.exists(mappath)
1537
1538 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1539 # it to clobbered_blocks so that it will be written to the target
1540 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1541 clobbered_blocks = "0"
1542
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001543 image = sparse_img.SparseImage(
1544 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1545 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001546
1547 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1548 # if they contain all zeros. We can't reconstruct such a file from its block
1549 # list. Tag such entries accordingly. (Bug: 65213616)
1550 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001551 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001552 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001553 continue
1554
Tom Cherryd14b8952018-08-09 14:26:00 -07001555 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1556 # filename listed in system.map may contain an additional leading slash
1557 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1558 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001559 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001560
Tom Cherryd14b8952018-08-09 14:26:00 -07001561 # Special handling another case, where files not under /system
1562 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001563 if which == 'system' and not arcname.startswith('SYSTEM'):
1564 arcname = 'ROOT/' + arcname
1565
1566 assert arcname in input_zip.namelist(), \
1567 "Failed to find the ZIP entry for {}".format(entry)
1568
Tao Baoc765cca2018-01-31 17:32:40 -08001569 info = input_zip.getinfo(arcname)
1570 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001571
1572 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001573 # image, check the original block list to determine its completeness. Note
1574 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001575 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001576 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001577
Tao Baoc765cca2018-01-31 17:32:40 -08001578 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1579 ranges.extra['incomplete'] = True
1580
1581 return image
1582
1583
Doug Zongkereef39442009-04-02 12:14:19 -07001584def GetKeyPasswords(keylist):
1585 """Given a list of keys, prompt the user to enter passwords for
1586 those which require them. Return a {key: password} dict. password
1587 will be None if the key has no password."""
1588
Doug Zongker8ce7c252009-05-22 13:34:54 -07001589 no_passwords = []
1590 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001591 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001592 devnull = open("/dev/null", "w+b")
1593 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001594 # We don't need a password for things that aren't really keys.
1595 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001596 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001597 continue
1598
T.R. Fullhart37e10522013-03-18 10:31:26 -07001599 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001600 "-inform", "DER", "-nocrypt"],
1601 stdin=devnull.fileno(),
1602 stdout=devnull.fileno(),
1603 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001604 p.communicate()
1605 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001606 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001607 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001608 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001609 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1610 "-inform", "DER", "-passin", "pass:"],
1611 stdin=devnull.fileno(),
1612 stdout=devnull.fileno(),
1613 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001614 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001615 if p.returncode == 0:
1616 # Encrypted key with empty string as password.
1617 key_passwords[k] = ''
1618 elif stderr.startswith('Error decrypting key'):
1619 # Definitely encrypted key.
1620 # It would have said "Error reading key" if it didn't parse correctly.
1621 need_passwords.append(k)
1622 else:
1623 # Potentially, a type of key that openssl doesn't understand.
1624 # We'll let the routines in signapk.jar handle it.
1625 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001626 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001627
T.R. Fullhart37e10522013-03-18 10:31:26 -07001628 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001629 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001630 return key_passwords
1631
1632
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001633def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001634 """Gets the minSdkVersion declared in the APK.
1635
changho.shin0f125362019-07-08 10:59:00 +09001636 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001637 This can be both a decimal number (API Level) or a codename.
1638
1639 Args:
1640 apk_name: The APK filename.
1641
1642 Returns:
1643 The parsed SDK version string.
1644
1645 Raises:
1646 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001647 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001648 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001649 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001650 stderr=subprocess.PIPE)
1651 stdoutdata, stderrdata = proc.communicate()
1652 if proc.returncode != 0:
1653 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001654 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001655 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001656
Tao Baof47bf0f2018-03-21 23:28:51 -07001657 for line in stdoutdata.split("\n"):
1658 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001659 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1660 if m:
1661 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001662 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001663
1664
1665def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001666 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001667
Tao Baof47bf0f2018-03-21 23:28:51 -07001668 If minSdkVersion is set to a codename, it is translated to a number using the
1669 provided map.
1670
1671 Args:
1672 apk_name: The APK filename.
1673
1674 Returns:
1675 The parsed SDK version number.
1676
1677 Raises:
1678 ExternalError: On failing to get the min SDK version number.
1679 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001680 version = GetMinSdkVersion(apk_name)
1681 try:
1682 return int(version)
1683 except ValueError:
1684 # Not a decimal number. Codename?
1685 if version in codename_to_api_level_map:
1686 return codename_to_api_level_map[version]
1687 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001688 raise ExternalError(
1689 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1690 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001691
1692
1693def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001694 codename_to_api_level_map=None, whole_file=False,
1695 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001696 """Sign the input_name zip/jar/apk, producing output_name. Use the
1697 given key and password (the latter may be None if the key does not
1698 have a password.
1699
Doug Zongker951495f2009-08-14 12:44:19 -07001700 If whole_file is true, use the "-w" option to SignApk to embed a
1701 signature that covers the whole file in the archive comment of the
1702 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001703
1704 min_api_level is the API Level (int) of the oldest platform this file may end
1705 up on. If not specified for an APK, the API Level is obtained by interpreting
1706 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1707
1708 codename_to_api_level_map is needed to translate the codename which may be
1709 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001710
1711 Caller may optionally specify extra args to be passed to SignApk, which
1712 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001713 """
Tao Bao76def242017-11-21 09:25:31 -08001714 if codename_to_api_level_map is None:
1715 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001716 if extra_signapk_args is None:
1717 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001718
Alex Klyubin9667b182015-12-10 13:38:50 -08001719 java_library_path = os.path.join(
1720 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1721
Tao Baoe95540e2016-11-08 12:08:53 -08001722 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1723 ["-Djava.library.path=" + java_library_path,
1724 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001725 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001726 if whole_file:
1727 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001728
1729 min_sdk_version = min_api_level
1730 if min_sdk_version is None:
1731 if not whole_file:
1732 min_sdk_version = GetMinSdkVersionInt(
1733 input_name, codename_to_api_level_map)
1734 if min_sdk_version is not None:
1735 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1736
T.R. Fullhart37e10522013-03-18 10:31:26 -07001737 cmd.extend([key + OPTIONS.public_key_suffix,
1738 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001739 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001740
Tao Bao73dd4f42018-10-04 16:25:33 -07001741 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001742 if password is not None:
1743 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001744 stdoutdata, _ = proc.communicate(password)
1745 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001746 raise ExternalError(
1747 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001748 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001749
Doug Zongkereef39442009-04-02 12:14:19 -07001750
Doug Zongker37974732010-09-16 17:44:38 -07001751def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001752 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001753
Tao Bao9dd909e2017-11-14 11:27:32 -08001754 For non-AVB images, raise exception if the data is too big. Print a warning
1755 if the data is nearing the maximum size.
1756
1757 For AVB images, the actual image size should be identical to the limit.
1758
1759 Args:
1760 data: A string that contains all the data for the partition.
1761 target: The partition name. The ".img" suffix is optional.
1762 info_dict: The dict to be looked up for relevant info.
1763 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001764 if target.endswith(".img"):
1765 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001766 mount_point = "/" + target
1767
Ying Wangf8824af2014-06-03 14:07:27 -07001768 fs_type = None
1769 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001770 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001771 if mount_point == "/userdata":
1772 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001773 p = info_dict["fstab"][mount_point]
1774 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001775 device = p.device
1776 if "/" in device:
1777 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001778 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001779 if not fs_type or not limit:
1780 return
Doug Zongkereef39442009-04-02 12:14:19 -07001781
Andrew Boie0f9aec82012-02-14 09:32:52 -08001782 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001783 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1784 # path.
1785 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1786 if size != limit:
1787 raise ExternalError(
1788 "Mismatching image size for %s: expected %d actual %d" % (
1789 target, limit, size))
1790 else:
1791 pct = float(size) * 100.0 / limit
1792 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1793 if pct >= 99.0:
1794 raise ExternalError(msg)
1795 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001796 logger.warning("\n WARNING: %s\n", msg)
1797 else:
1798 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001799
1800
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001801def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001802 """Parses the APK certs info from a given target-files zip.
1803
1804 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1805 tuple with the following elements: (1) a dictionary that maps packages to
1806 certs (based on the "certificate" and "private_key" attributes in the file;
1807 (2) a string representing the extension of compressed APKs in the target files
1808 (e.g ".gz", ".bro").
1809
1810 Args:
1811 tf_zip: The input target_files ZipFile (already open).
1812
1813 Returns:
1814 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1815 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1816 no compressed APKs.
1817 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001818 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001819 compressed_extension = None
1820
Tao Bao0f990332017-09-08 19:02:54 -07001821 # META/apkcerts.txt contains the info for _all_ the packages known at build
1822 # time. Filter out the ones that are not installed.
1823 installed_files = set()
1824 for name in tf_zip.namelist():
1825 basename = os.path.basename(name)
1826 if basename:
1827 installed_files.add(basename)
1828
Tao Baoda30cfa2017-12-01 16:19:46 -08001829 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001830 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001831 if not line:
1832 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001833 m = re.match(
1834 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001835 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1836 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001837 line)
1838 if not m:
1839 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001840
Tao Bao818ddf52018-01-05 11:17:34 -08001841 matches = m.groupdict()
1842 cert = matches["CERT"]
1843 privkey = matches["PRIVKEY"]
1844 name = matches["NAME"]
1845 this_compressed_extension = matches["COMPRESSED"]
1846
1847 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1848 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1849 if cert in SPECIAL_CERT_STRINGS and not privkey:
1850 certmap[name] = cert
1851 elif (cert.endswith(OPTIONS.public_key_suffix) and
1852 privkey.endswith(OPTIONS.private_key_suffix) and
1853 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1854 certmap[name] = cert[:-public_key_suffix_len]
1855 else:
1856 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1857
1858 if not this_compressed_extension:
1859 continue
1860
1861 # Only count the installed files.
1862 filename = name + '.' + this_compressed_extension
1863 if filename not in installed_files:
1864 continue
1865
1866 # Make sure that all the values in the compression map have the same
1867 # extension. We don't support multiple compression methods in the same
1868 # system image.
1869 if compressed_extension:
1870 if this_compressed_extension != compressed_extension:
1871 raise ValueError(
1872 "Multiple compressed extensions: {} vs {}".format(
1873 compressed_extension, this_compressed_extension))
1874 else:
1875 compressed_extension = this_compressed_extension
1876
1877 return (certmap,
1878 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001879
1880
Doug Zongkereef39442009-04-02 12:14:19 -07001881COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001882Global options
1883
1884 -p (--path) <dir>
1885 Prepend <dir>/bin to the list of places to search for binaries run by this
1886 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001887
Doug Zongker05d3dea2009-06-22 11:32:31 -07001888 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001889 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001890
Tao Bao30df8b42018-04-23 15:32:53 -07001891 -x (--extra) <key=value>
1892 Add a key/value pair to the 'extras' dict, which device-specific extension
1893 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001894
Doug Zongkereef39442009-04-02 12:14:19 -07001895 -v (--verbose)
1896 Show command lines being executed.
1897
1898 -h (--help)
1899 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001900
1901 --logfile <file>
1902 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001903"""
1904
1905def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001906 print(docstring.rstrip("\n"))
1907 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001908
1909
1910def ParseOptions(argv,
1911 docstring,
1912 extra_opts="", extra_long_opts=(),
1913 extra_option_handler=None):
1914 """Parse the options in argv and return any arguments that aren't
1915 flags. docstring is the calling module's docstring, to be displayed
1916 for errors and -h. extra_opts and extra_long_opts are for flags
1917 defined by the caller, which are processed by passing them to
1918 extra_option_handler."""
1919
1920 try:
1921 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001922 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001923 ["help", "verbose", "path=", "signapk_path=",
1924 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08001925 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001926 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1927 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07001928 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
1929 "aftl_key_path=", "aftl_manufacturer_key_path=",
1930 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001931 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001932 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001933 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001934 sys.exit(2)
1935
Doug Zongkereef39442009-04-02 12:14:19 -07001936 for o, a in opts:
1937 if o in ("-h", "--help"):
1938 Usage(docstring)
1939 sys.exit()
1940 elif o in ("-v", "--verbose"):
1941 OPTIONS.verbose = True
1942 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001943 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001944 elif o in ("--signapk_path",):
1945 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001946 elif o in ("--signapk_shared_library_path",):
1947 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001948 elif o in ("--extra_signapk_args",):
1949 OPTIONS.extra_signapk_args = shlex.split(a)
1950 elif o in ("--java_path",):
1951 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001952 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001953 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08001954 elif o in ("--android_jar_path",):
1955 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001956 elif o in ("--public_key_suffix",):
1957 OPTIONS.public_key_suffix = a
1958 elif o in ("--private_key_suffix",):
1959 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001960 elif o in ("--boot_signer_path",):
1961 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001962 elif o in ("--boot_signer_args",):
1963 OPTIONS.boot_signer_args = shlex.split(a)
1964 elif o in ("--verity_signer_path",):
1965 OPTIONS.verity_signer_path = a
1966 elif o in ("--verity_signer_args",):
1967 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07001968 elif o in ("--aftl_tool_path",):
1969 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08001970 elif o in ("--aftl_server",):
1971 OPTIONS.aftl_server = a
1972 elif o in ("--aftl_key_path",):
1973 OPTIONS.aftl_key_path = a
1974 elif o in ("--aftl_manufacturer_key_path",):
1975 OPTIONS.aftl_manufacturer_key_path = a
1976 elif o in ("--aftl_signer_helper",):
1977 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07001978 elif o in ("-s", "--device_specific"):
1979 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001980 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001981 key, value = a.split("=", 1)
1982 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001983 elif o in ("--logfile",):
1984 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001985 else:
1986 if extra_option_handler is None or not extra_option_handler(o, a):
1987 assert False, "unknown option \"%s\"" % (o,)
1988
Doug Zongker85448772014-09-09 14:59:20 -07001989 if OPTIONS.search_path:
1990 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1991 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001992
1993 return args
1994
1995
Tao Bao4c851b12016-09-19 13:54:38 -07001996def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001997 """Make a temp file and add it to the list of things to be deleted
1998 when Cleanup() is called. Return the filename."""
1999 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2000 os.close(fd)
2001 OPTIONS.tempfiles.append(fn)
2002 return fn
2003
2004
Tao Bao1c830bf2017-12-25 10:43:47 -08002005def MakeTempDir(prefix='tmp', suffix=''):
2006 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2007
2008 Returns:
2009 The absolute pathname of the new directory.
2010 """
2011 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2012 OPTIONS.tempfiles.append(dir_name)
2013 return dir_name
2014
2015
Doug Zongkereef39442009-04-02 12:14:19 -07002016def Cleanup():
2017 for i in OPTIONS.tempfiles:
2018 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002019 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002020 else:
2021 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002022 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002023
2024
2025class PasswordManager(object):
2026 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002027 self.editor = os.getenv("EDITOR")
2028 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002029
2030 def GetPasswords(self, items):
2031 """Get passwords corresponding to each string in 'items',
2032 returning a dict. (The dict may have keys in addition to the
2033 values in 'items'.)
2034
2035 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2036 user edit that file to add more needed passwords. If no editor is
2037 available, or $ANDROID_PW_FILE isn't define, prompts the user
2038 interactively in the ordinary way.
2039 """
2040
2041 current = self.ReadFile()
2042
2043 first = True
2044 while True:
2045 missing = []
2046 for i in items:
2047 if i not in current or not current[i]:
2048 missing.append(i)
2049 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002050 if not missing:
2051 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002052
2053 for i in missing:
2054 current[i] = ""
2055
2056 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002057 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002058 if sys.version_info[0] >= 3:
2059 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002060 answer = raw_input("try to edit again? [y]> ").strip()
2061 if answer and answer[0] not in 'yY':
2062 raise RuntimeError("key passwords unavailable")
2063 first = False
2064
2065 current = self.UpdateAndReadFile(current)
2066
Dan Albert8b72aef2015-03-23 19:13:21 -07002067 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002068 """Prompt the user to enter a value (password) for each key in
2069 'current' whose value is fales. Returns a new dict with all the
2070 values.
2071 """
2072 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002073 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002074 if v:
2075 result[k] = v
2076 else:
2077 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002078 result[k] = getpass.getpass(
2079 "Enter password for %s key> " % k).strip()
2080 if result[k]:
2081 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002082 return result
2083
2084 def UpdateAndReadFile(self, current):
2085 if not self.editor or not self.pwfile:
2086 return self.PromptResult(current)
2087
2088 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002089 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002090 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2091 f.write("# (Additional spaces are harmless.)\n\n")
2092
2093 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002094 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002095 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002096 f.write("[[[ %s ]]] %s\n" % (v, k))
2097 if not v and first_line is None:
2098 # position cursor on first line with no password.
2099 first_line = i + 4
2100 f.close()
2101
Tao Bao986ee862018-10-04 15:46:16 -07002102 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002103
2104 return self.ReadFile()
2105
2106 def ReadFile(self):
2107 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002108 if self.pwfile is None:
2109 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002110 try:
2111 f = open(self.pwfile, "r")
2112 for line in f:
2113 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002114 if not line or line[0] == '#':
2115 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002116 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2117 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002118 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002119 else:
2120 result[m.group(2)] = m.group(1)
2121 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002122 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002123 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002124 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002125 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002126
2127
Dan Albert8e0178d2015-01-27 15:53:15 -08002128def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2129 compress_type=None):
2130 import datetime
2131
2132 # http://b/18015246
2133 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2134 # for files larger than 2GiB. We can work around this by adjusting their
2135 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2136 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2137 # it isn't clear to me exactly what circumstances cause this).
2138 # `zipfile.write()` must be used directly to work around this.
2139 #
2140 # This mess can be avoided if we port to python3.
2141 saved_zip64_limit = zipfile.ZIP64_LIMIT
2142 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2143
2144 if compress_type is None:
2145 compress_type = zip_file.compression
2146 if arcname is None:
2147 arcname = filename
2148
2149 saved_stat = os.stat(filename)
2150
2151 try:
2152 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2153 # file to be zipped and reset it when we're done.
2154 os.chmod(filename, perms)
2155
2156 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002157 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2158 # intentional. zip stores datetimes in local time without a time zone
2159 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2160 # in the zip archive.
2161 local_epoch = datetime.datetime.fromtimestamp(0)
2162 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002163 os.utime(filename, (timestamp, timestamp))
2164
2165 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2166 finally:
2167 os.chmod(filename, saved_stat.st_mode)
2168 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2169 zipfile.ZIP64_LIMIT = saved_zip64_limit
2170
2171
Tao Bao58c1b962015-05-20 09:32:18 -07002172def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002173 compress_type=None):
2174 """Wrap zipfile.writestr() function to work around the zip64 limit.
2175
2176 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2177 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2178 when calling crc32(bytes).
2179
2180 But it still works fine to write a shorter string into a large zip file.
2181 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2182 when we know the string won't be too long.
2183 """
2184
2185 saved_zip64_limit = zipfile.ZIP64_LIMIT
2186 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2187
2188 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2189 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002190 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002191 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002192 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002193 else:
Tao Baof3282b42015-04-01 11:21:55 -07002194 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002195 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2196 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2197 # such a case (since
2198 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2199 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2200 # permission bits. We follow the logic in Python 3 to get consistent
2201 # behavior between using the two versions.
2202 if not zinfo.external_attr:
2203 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002204
2205 # If compress_type is given, it overrides the value in zinfo.
2206 if compress_type is not None:
2207 zinfo.compress_type = compress_type
2208
Tao Bao58c1b962015-05-20 09:32:18 -07002209 # If perms is given, it has a priority.
2210 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002211 # If perms doesn't set the file type, mark it as a regular file.
2212 if perms & 0o770000 == 0:
2213 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002214 zinfo.external_attr = perms << 16
2215
Tao Baof3282b42015-04-01 11:21:55 -07002216 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002217 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2218
Dan Albert8b72aef2015-03-23 19:13:21 -07002219 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002220 zipfile.ZIP64_LIMIT = saved_zip64_limit
2221
2222
Tao Bao89d7ab22017-12-14 17:05:33 -08002223def ZipDelete(zip_filename, entries):
2224 """Deletes entries from a ZIP file.
2225
2226 Since deleting entries from a ZIP file is not supported, it shells out to
2227 'zip -d'.
2228
2229 Args:
2230 zip_filename: The name of the ZIP file.
2231 entries: The name of the entry, or the list of names to be deleted.
2232
2233 Raises:
2234 AssertionError: In case of non-zero return from 'zip'.
2235 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002236 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002237 entries = [entries]
2238 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002239 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002240
2241
Tao Baof3282b42015-04-01 11:21:55 -07002242def ZipClose(zip_file):
2243 # http://b/18015246
2244 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2245 # central directory.
2246 saved_zip64_limit = zipfile.ZIP64_LIMIT
2247 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2248
2249 zip_file.close()
2250
2251 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002252
2253
2254class DeviceSpecificParams(object):
2255 module = None
2256 def __init__(self, **kwargs):
2257 """Keyword arguments to the constructor become attributes of this
2258 object, which is passed to all functions in the device-specific
2259 module."""
Tao Bao38884282019-07-10 22:20:56 -07002260 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002261 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002262 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002263
2264 if self.module is None:
2265 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002266 if not path:
2267 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002268 try:
2269 if os.path.isdir(path):
2270 info = imp.find_module("releasetools", [path])
2271 else:
2272 d, f = os.path.split(path)
2273 b, x = os.path.splitext(f)
2274 if x == ".py":
2275 f = b
2276 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002277 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002278 self.module = imp.load_module("device_specific", *info)
2279 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002280 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002281
2282 def _DoCall(self, function_name, *args, **kwargs):
2283 """Call the named function in the device-specific module, passing
2284 the given args and kwargs. The first argument to the call will be
2285 the DeviceSpecific object itself. If there is no module, or the
2286 module does not define the function, return the value of the
2287 'default' kwarg (which itself defaults to None)."""
2288 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002289 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002290 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2291
2292 def FullOTA_Assertions(self):
2293 """Called after emitting the block of assertions at the top of a
2294 full OTA package. Implementations can add whatever additional
2295 assertions they like."""
2296 return self._DoCall("FullOTA_Assertions")
2297
Doug Zongkere5ff5902012-01-17 10:55:37 -08002298 def FullOTA_InstallBegin(self):
2299 """Called at the start of full OTA installation."""
2300 return self._DoCall("FullOTA_InstallBegin")
2301
Yifan Hong10c530d2018-12-27 17:34:18 -08002302 def FullOTA_GetBlockDifferences(self):
2303 """Called during full OTA installation and verification.
2304 Implementation should return a list of BlockDifference objects describing
2305 the update on each additional partitions.
2306 """
2307 return self._DoCall("FullOTA_GetBlockDifferences")
2308
Doug Zongker05d3dea2009-06-22 11:32:31 -07002309 def FullOTA_InstallEnd(self):
2310 """Called at the end of full OTA installation; typically this is
2311 used to install the image for the device's baseband processor."""
2312 return self._DoCall("FullOTA_InstallEnd")
2313
2314 def IncrementalOTA_Assertions(self):
2315 """Called after emitting the block of assertions at the top of an
2316 incremental OTA package. Implementations can add whatever
2317 additional assertions they like."""
2318 return self._DoCall("IncrementalOTA_Assertions")
2319
Doug Zongkere5ff5902012-01-17 10:55:37 -08002320 def IncrementalOTA_VerifyBegin(self):
2321 """Called at the start of the verification phase of incremental
2322 OTA installation; additional checks can be placed here to abort
2323 the script before any changes are made."""
2324 return self._DoCall("IncrementalOTA_VerifyBegin")
2325
Doug Zongker05d3dea2009-06-22 11:32:31 -07002326 def IncrementalOTA_VerifyEnd(self):
2327 """Called at the end of the verification phase of incremental OTA
2328 installation; additional checks can be placed here to abort the
2329 script before any changes are made."""
2330 return self._DoCall("IncrementalOTA_VerifyEnd")
2331
Doug Zongkere5ff5902012-01-17 10:55:37 -08002332 def IncrementalOTA_InstallBegin(self):
2333 """Called at the start of incremental OTA installation (after
2334 verification is complete)."""
2335 return self._DoCall("IncrementalOTA_InstallBegin")
2336
Yifan Hong10c530d2018-12-27 17:34:18 -08002337 def IncrementalOTA_GetBlockDifferences(self):
2338 """Called during incremental OTA installation and verification.
2339 Implementation should return a list of BlockDifference objects describing
2340 the update on each additional partitions.
2341 """
2342 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2343
Doug Zongker05d3dea2009-06-22 11:32:31 -07002344 def IncrementalOTA_InstallEnd(self):
2345 """Called at the end of incremental OTA installation; typically
2346 this is used to install the image for the device's baseband
2347 processor."""
2348 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002349
Tao Bao9bc6bb22015-11-09 16:58:28 -08002350 def VerifyOTA_Assertions(self):
2351 return self._DoCall("VerifyOTA_Assertions")
2352
Tao Bao76def242017-11-21 09:25:31 -08002353
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002354class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002355 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002356 self.name = name
2357 self.data = data
2358 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002359 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002360 self.sha1 = sha1(data).hexdigest()
2361
2362 @classmethod
2363 def FromLocalFile(cls, name, diskname):
2364 f = open(diskname, "rb")
2365 data = f.read()
2366 f.close()
2367 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002368
2369 def WriteToTemp(self):
2370 t = tempfile.NamedTemporaryFile()
2371 t.write(self.data)
2372 t.flush()
2373 return t
2374
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002375 def WriteToDir(self, d):
2376 with open(os.path.join(d, self.name), "wb") as fp:
2377 fp.write(self.data)
2378
Geremy Condra36bd3652014-02-06 19:45:10 -08002379 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002380 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002381
Tao Bao76def242017-11-21 09:25:31 -08002382
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002383DIFF_PROGRAM_BY_EXT = {
2384 ".gz" : "imgdiff",
2385 ".zip" : ["imgdiff", "-z"],
2386 ".jar" : ["imgdiff", "-z"],
2387 ".apk" : ["imgdiff", "-z"],
2388 ".img" : "imgdiff",
2389 }
2390
Tao Bao76def242017-11-21 09:25:31 -08002391
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002392class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002393 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002394 self.tf = tf
2395 self.sf = sf
2396 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002397 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002398
2399 def ComputePatch(self):
2400 """Compute the patch (as a string of data) needed to turn sf into
2401 tf. Returns the same tuple as GetPatch()."""
2402
2403 tf = self.tf
2404 sf = self.sf
2405
Doug Zongker24cd2802012-08-14 16:36:15 -07002406 if self.diff_program:
2407 diff_program = self.diff_program
2408 else:
2409 ext = os.path.splitext(tf.name)[1]
2410 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002411
2412 ttemp = tf.WriteToTemp()
2413 stemp = sf.WriteToTemp()
2414
2415 ext = os.path.splitext(tf.name)[1]
2416
2417 try:
2418 ptemp = tempfile.NamedTemporaryFile()
2419 if isinstance(diff_program, list):
2420 cmd = copy.copy(diff_program)
2421 else:
2422 cmd = [diff_program]
2423 cmd.append(stemp.name)
2424 cmd.append(ttemp.name)
2425 cmd.append(ptemp.name)
2426 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002427 err = []
2428 def run():
2429 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002430 if e:
2431 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002432 th = threading.Thread(target=run)
2433 th.start()
2434 th.join(timeout=300) # 5 mins
2435 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002436 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002437 p.terminate()
2438 th.join(5)
2439 if th.is_alive():
2440 p.kill()
2441 th.join()
2442
Tianjie Xua2a9f992018-01-05 15:15:54 -08002443 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002444 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002445 self.patch = None
2446 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002447 diff = ptemp.read()
2448 finally:
2449 ptemp.close()
2450 stemp.close()
2451 ttemp.close()
2452
2453 self.patch = diff
2454 return self.tf, self.sf, self.patch
2455
2456
2457 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002458 """Returns a tuple of (target_file, source_file, patch_data).
2459
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002460 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002461 computing the patch failed.
2462 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002463 return self.tf, self.sf, self.patch
2464
2465
2466def ComputeDifferences(diffs):
2467 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002468 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002469
2470 # Do the largest files first, to try and reduce the long-pole effect.
2471 by_size = [(i.tf.size, i) for i in diffs]
2472 by_size.sort(reverse=True)
2473 by_size = [i[1] for i in by_size]
2474
2475 lock = threading.Lock()
2476 diff_iter = iter(by_size) # accessed under lock
2477
2478 def worker():
2479 try:
2480 lock.acquire()
2481 for d in diff_iter:
2482 lock.release()
2483 start = time.time()
2484 d.ComputePatch()
2485 dur = time.time() - start
2486 lock.acquire()
2487
2488 tf, sf, patch = d.GetPatch()
2489 if sf.name == tf.name:
2490 name = tf.name
2491 else:
2492 name = "%s (%s)" % (tf.name, sf.name)
2493 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002494 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002495 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002496 logger.info(
2497 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2498 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002499 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002500 except Exception:
2501 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002502 raise
2503
2504 # start worker threads; wait for them all to finish.
2505 threads = [threading.Thread(target=worker)
2506 for i in range(OPTIONS.worker_threads)]
2507 for th in threads:
2508 th.start()
2509 while threads:
2510 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002511
2512
Dan Albert8b72aef2015-03-23 19:13:21 -07002513class BlockDifference(object):
2514 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002515 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002516 self.tgt = tgt
2517 self.src = src
2518 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002519 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002520 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002521
Tao Baodd2a5892015-03-12 12:32:37 -07002522 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002523 version = max(
2524 int(i) for i in
2525 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002526 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002527 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002528
Tianjie Xu41976c72019-07-03 13:57:01 -07002529 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2530 version=self.version,
2531 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002532 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002533 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002534 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002535 self.touched_src_ranges = b.touched_src_ranges
2536 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002537
Yifan Hong10c530d2018-12-27 17:34:18 -08002538 # On devices with dynamic partitions, for new partitions,
2539 # src is None but OPTIONS.source_info_dict is not.
2540 if OPTIONS.source_info_dict is None:
2541 is_dynamic_build = OPTIONS.info_dict.get(
2542 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002543 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002544 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002545 is_dynamic_build = OPTIONS.source_info_dict.get(
2546 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002547 is_dynamic_source = partition in shlex.split(
2548 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002549
Yifan Hongbb2658d2019-01-25 12:30:58 -08002550 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002551 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2552
Yifan Hongbb2658d2019-01-25 12:30:58 -08002553 # For dynamic partitions builds, check partition list in both source
2554 # and target build because new partitions may be added, and existing
2555 # partitions may be removed.
2556 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2557
Yifan Hong10c530d2018-12-27 17:34:18 -08002558 if is_dynamic:
2559 self.device = 'map_partition("%s")' % partition
2560 else:
2561 if OPTIONS.source_info_dict is None:
2562 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2563 else:
2564 _, device_path = GetTypeAndDevice("/" + partition,
2565 OPTIONS.source_info_dict)
2566 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002567
Tao Baod8d14be2016-02-04 14:26:02 -08002568 @property
2569 def required_cache(self):
2570 return self._required_cache
2571
Tao Bao76def242017-11-21 09:25:31 -08002572 def WriteScript(self, script, output_zip, progress=None,
2573 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002574 if not self.src:
2575 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002576 script.Print("Patching %s image unconditionally..." % (self.partition,))
2577 else:
2578 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002579
Dan Albert8b72aef2015-03-23 19:13:21 -07002580 if progress:
2581 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002582 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002583
2584 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002585 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002586
Tao Bao9bc6bb22015-11-09 16:58:28 -08002587 def WriteStrictVerifyScript(self, script):
2588 """Verify all the blocks in the care_map, including clobbered blocks.
2589
2590 This differs from the WriteVerifyScript() function: a) it prints different
2591 error messages; b) it doesn't allow half-way updated images to pass the
2592 verification."""
2593
2594 partition = self.partition
2595 script.Print("Verifying %s..." % (partition,))
2596 ranges = self.tgt.care_map
2597 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002598 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002599 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2600 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002601 self.device, ranges_str,
2602 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002603 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002604 script.AppendExtra("")
2605
Tao Baod522bdc2016-04-12 15:53:16 -07002606 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002607 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002608
2609 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002610 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002611 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002612
2613 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002614 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002615 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002616 ranges = self.touched_src_ranges
2617 expected_sha1 = self.touched_src_sha1
2618 else:
2619 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2620 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002621
2622 # No blocks to be checked, skipping.
2623 if not ranges:
2624 return
2625
Tao Bao5ece99d2015-05-12 11:42:31 -07002626 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002627 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002628 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002629 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2630 '"%s.patch.dat")) then' % (
2631 self.device, ranges_str, expected_sha1,
2632 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002633 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002634 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002635
Tianjie Xufc3422a2015-12-15 11:53:59 -08002636 if self.version >= 4:
2637
2638 # Bug: 21124327
2639 # When generating incrementals for the system and vendor partitions in
2640 # version 4 or newer, explicitly check the first block (which contains
2641 # the superblock) of the partition to see if it's what we expect. If
2642 # this check fails, give an explicit log message about the partition
2643 # having been remounted R/W (the most likely explanation).
2644 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002645 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002646
2647 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002648 if partition == "system":
2649 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2650 else:
2651 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002652 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002653 'ifelse (block_image_recover({device}, "{ranges}") && '
2654 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002655 'package_extract_file("{partition}.transfer.list"), '
2656 '"{partition}.new.dat", "{partition}.patch.dat"), '
2657 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002658 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002659 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002660 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002661
Tao Baodd2a5892015-03-12 12:32:37 -07002662 # Abort the OTA update. Note that the incremental OTA cannot be applied
2663 # even if it may match the checksum of the target partition.
2664 # a) If version < 3, operations like move and erase will make changes
2665 # unconditionally and damage the partition.
2666 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002667 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002668 if partition == "system":
2669 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2670 else:
2671 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2672 script.AppendExtra((
2673 'abort("E%d: %s partition has unexpected contents");\n'
2674 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002675
Yifan Hong10c530d2018-12-27 17:34:18 -08002676 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002677 partition = self.partition
2678 script.Print('Verifying the updated %s image...' % (partition,))
2679 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2680 ranges = self.tgt.care_map
2681 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002682 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002683 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002684 self.device, ranges_str,
2685 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002686
2687 # Bug: 20881595
2688 # Verify that extended blocks are really zeroed out.
2689 if self.tgt.extended:
2690 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002691 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002692 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002693 self.device, ranges_str,
2694 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002695 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002696 if partition == "system":
2697 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2698 else:
2699 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002700 script.AppendExtra(
2701 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002702 ' abort("E%d: %s partition has unexpected non-zero contents after '
2703 'OTA update");\n'
2704 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002705 else:
2706 script.Print('Verified the updated %s image.' % (partition,))
2707
Tianjie Xu209db462016-05-24 17:34:52 -07002708 if partition == "system":
2709 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2710 else:
2711 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2712
Tao Bao5fcaaef2015-06-01 13:40:49 -07002713 script.AppendExtra(
2714 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002715 ' abort("E%d: %s partition has unexpected contents after OTA '
2716 'update");\n'
2717 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002718
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002719 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002720 ZipWrite(output_zip,
2721 '{}.transfer.list'.format(self.path),
2722 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002723
Tao Bao76def242017-11-21 09:25:31 -08002724 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2725 # its size. Quailty 9 almost triples the compression time but doesn't
2726 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002727 # zip | brotli(quality 6) | brotli(quality 9)
2728 # compressed_size: 942M | 869M (~8% reduced) | 854M
2729 # compression_time: 75s | 265s | 719s
2730 # decompression_time: 15s | 25s | 25s
2731
2732 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002733 brotli_cmd = ['brotli', '--quality=6',
2734 '--output={}.new.dat.br'.format(self.path),
2735 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002736 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002737 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002738
2739 new_data_name = '{}.new.dat.br'.format(self.partition)
2740 ZipWrite(output_zip,
2741 '{}.new.dat.br'.format(self.path),
2742 new_data_name,
2743 compress_type=zipfile.ZIP_STORED)
2744 else:
2745 new_data_name = '{}.new.dat'.format(self.partition)
2746 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2747
Dan Albert8e0178d2015-01-27 15:53:15 -08002748 ZipWrite(output_zip,
2749 '{}.patch.dat'.format(self.path),
2750 '{}.patch.dat'.format(self.partition),
2751 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002752
Tianjie Xu209db462016-05-24 17:34:52 -07002753 if self.partition == "system":
2754 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2755 else:
2756 code = ErrorCode.VENDOR_UPDATE_FAILURE
2757
Yifan Hong10c530d2018-12-27 17:34:18 -08002758 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002759 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002760 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002761 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002762 device=self.device, partition=self.partition,
2763 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002764 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002765
Dan Albert8b72aef2015-03-23 19:13:21 -07002766 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002767 data = source.ReadRangeSet(ranges)
2768 ctx = sha1()
2769
2770 for p in data:
2771 ctx.update(p)
2772
2773 return ctx.hexdigest()
2774
Tao Baoe9b61912015-07-09 17:37:49 -07002775 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2776 """Return the hash value for all zero blocks."""
2777 zero_block = '\x00' * 4096
2778 ctx = sha1()
2779 for _ in range(num_blocks):
2780 ctx.update(zero_block)
2781
2782 return ctx.hexdigest()
2783
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002784
Tianjie Xu41976c72019-07-03 13:57:01 -07002785# Expose these two classes to support vendor-specific scripts
2786DataImage = images.DataImage
2787EmptyImage = images.EmptyImage
2788
Tao Bao76def242017-11-21 09:25:31 -08002789
Doug Zongker96a57e72010-09-26 14:57:41 -07002790# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002791PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002792 "ext4": "EMMC",
2793 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002794 "f2fs": "EMMC",
2795 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002796}
Doug Zongker96a57e72010-09-26 14:57:41 -07002797
Tao Bao76def242017-11-21 09:25:31 -08002798
Doug Zongker96a57e72010-09-26 14:57:41 -07002799def GetTypeAndDevice(mount_point, info):
2800 fstab = info["fstab"]
2801 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002802 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2803 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002804 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002805 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002806
2807
2808def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002809 """Parses and converts a PEM-encoded certificate into DER-encoded.
2810
2811 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2812
2813 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002814 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002815 """
2816 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002817 save = False
2818 for line in data.split("\n"):
2819 if "--END CERTIFICATE--" in line:
2820 break
2821 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002822 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002823 if "--BEGIN CERTIFICATE--" in line:
2824 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002825 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002826 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002827
Tao Bao04e1f012018-02-04 12:13:35 -08002828
2829def ExtractPublicKey(cert):
2830 """Extracts the public key (PEM-encoded) from the given certificate file.
2831
2832 Args:
2833 cert: The certificate filename.
2834
2835 Returns:
2836 The public key string.
2837
2838 Raises:
2839 AssertionError: On non-zero return from 'openssl'.
2840 """
2841 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2842 # While openssl 1.1 writes the key into the given filename followed by '-out',
2843 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2844 # stdout instead.
2845 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2846 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2847 pubkey, stderrdata = proc.communicate()
2848 assert proc.returncode == 0, \
2849 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2850 return pubkey
2851
2852
Tao Bao1ac886e2019-06-26 11:58:22 -07002853def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002854 """Extracts the AVB public key from the given public or private key.
2855
2856 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002857 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002858 key: The input key file, which should be PEM-encoded public or private key.
2859
2860 Returns:
2861 The path to the extracted AVB public key file.
2862 """
2863 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2864 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002865 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002866 return output
2867
2868
Doug Zongker412c02f2014-02-13 10:58:24 -08002869def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2870 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002871 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002872
Tao Bao6d5d6232018-03-09 17:04:42 -08002873 Most of the space in the boot and recovery images is just the kernel, which is
2874 identical for the two, so the resulting patch should be efficient. Add it to
2875 the output zip, along with a shell script that is run from init.rc on first
2876 boot to actually do the patching and install the new recovery image.
2877
2878 Args:
2879 input_dir: The top-level input directory of the target-files.zip.
2880 output_sink: The callback function that writes the result.
2881 recovery_img: File object for the recovery image.
2882 boot_img: File objects for the boot image.
2883 info_dict: A dict returned by common.LoadInfoDict() on the input
2884 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002885 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002886 if info_dict is None:
2887 info_dict = OPTIONS.info_dict
2888
Tao Bao6d5d6232018-03-09 17:04:42 -08002889 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002890 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2891
2892 if board_uses_vendorimage:
2893 # In this case, the output sink is rooted at VENDOR
2894 recovery_img_path = "etc/recovery.img"
2895 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2896 sh_dir = "bin"
2897 else:
2898 # In this case the output sink is rooted at SYSTEM
2899 recovery_img_path = "vendor/etc/recovery.img"
2900 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2901 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002902
Tao Baof2cffbd2015-07-22 12:33:18 -07002903 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002904 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002905
2906 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002907 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002908 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002909 # With system-root-image, boot and recovery images will have mismatching
2910 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2911 # to handle such a case.
2912 if system_root_image:
2913 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002914 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002915 assert not os.path.exists(path)
2916 else:
2917 diff_program = ["imgdiff"]
2918 if os.path.exists(path):
2919 diff_program.append("-b")
2920 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002921 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002922 else:
2923 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002924
2925 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2926 _, _, patch = d.ComputePatch()
2927 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002928
Dan Albertebb19aa2015-03-27 19:11:53 -07002929 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002930 # The following GetTypeAndDevice()s need to use the path in the target
2931 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002932 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2933 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2934 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002935 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002936
Tao Baof2cffbd2015-07-22 12:33:18 -07002937 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002938
2939 # Note that we use /vendor to refer to the recovery resources. This will
2940 # work for a separate vendor partition mounted at /vendor or a
2941 # /system/vendor subdirectory on the system partition, for which init will
2942 # create a symlink from /vendor to /system/vendor.
2943
2944 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002945if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2946 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002947 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002948 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2949 log -t recovery "Installing new recovery image: succeeded" || \\
2950 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002951else
2952 log -t recovery "Recovery image already installed"
2953fi
2954""" % {'type': recovery_type,
2955 'device': recovery_device,
2956 'sha1': recovery_img.sha1,
2957 'size': recovery_img.size}
2958 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002959 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002960if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2961 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002962 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002963 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2964 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2965 log -t recovery "Installing new recovery image: succeeded" || \\
2966 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002967else
2968 log -t recovery "Recovery image already installed"
2969fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002970""" % {'boot_size': boot_img.size,
2971 'boot_sha1': boot_img.sha1,
2972 'recovery_size': recovery_img.size,
2973 'recovery_sha1': recovery_img.sha1,
2974 'boot_type': boot_type,
2975 'boot_device': boot_device,
2976 'recovery_type': recovery_type,
2977 'recovery_device': recovery_device,
2978 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002979
Bill Peckhame868aec2019-09-17 17:06:47 -07002980 # The install script location moved from /system/etc to /system/bin in the L
2981 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2982 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002983
Tao Bao32fcdab2018-10-12 10:30:39 -07002984 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002985
Tao Baoda30cfa2017-12-01 16:19:46 -08002986 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002987
2988
2989class DynamicPartitionUpdate(object):
2990 def __init__(self, src_group=None, tgt_group=None, progress=None,
2991 block_difference=None):
2992 self.src_group = src_group
2993 self.tgt_group = tgt_group
2994 self.progress = progress
2995 self.block_difference = block_difference
2996
2997 @property
2998 def src_size(self):
2999 if not self.block_difference:
3000 return 0
3001 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3002
3003 @property
3004 def tgt_size(self):
3005 if not self.block_difference:
3006 return 0
3007 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3008
3009 @staticmethod
3010 def _GetSparseImageSize(img):
3011 if not img:
3012 return 0
3013 return img.blocksize * img.total_blocks
3014
3015
3016class DynamicGroupUpdate(object):
3017 def __init__(self, src_size=None, tgt_size=None):
3018 # None: group does not exist. 0: no size limits.
3019 self.src_size = src_size
3020 self.tgt_size = tgt_size
3021
3022
3023class DynamicPartitionsDifference(object):
3024 def __init__(self, info_dict, block_diffs, progress_dict=None,
3025 source_info_dict=None):
3026 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003027 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003028
3029 self._remove_all_before_apply = False
3030 if source_info_dict is None:
3031 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003032 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003033
Tao Baof1113e92019-06-18 12:10:14 -07003034 block_diff_dict = collections.OrderedDict(
3035 [(e.partition, e) for e in block_diffs])
3036
Yifan Hong10c530d2018-12-27 17:34:18 -08003037 assert len(block_diff_dict) == len(block_diffs), \
3038 "Duplicated BlockDifference object for {}".format(
3039 [partition for partition, count in
3040 collections.Counter(e.partition for e in block_diffs).items()
3041 if count > 1])
3042
Yifan Hong79997e52019-01-23 16:56:19 -08003043 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003044
3045 for p, block_diff in block_diff_dict.items():
3046 self._partition_updates[p] = DynamicPartitionUpdate()
3047 self._partition_updates[p].block_difference = block_diff
3048
3049 for p, progress in progress_dict.items():
3050 if p in self._partition_updates:
3051 self._partition_updates[p].progress = progress
3052
3053 tgt_groups = shlex.split(info_dict.get(
3054 "super_partition_groups", "").strip())
3055 src_groups = shlex.split(source_info_dict.get(
3056 "super_partition_groups", "").strip())
3057
3058 for g in tgt_groups:
3059 for p in shlex.split(info_dict.get(
3060 "super_%s_partition_list" % g, "").strip()):
3061 assert p in self._partition_updates, \
3062 "{} is in target super_{}_partition_list but no BlockDifference " \
3063 "object is provided.".format(p, g)
3064 self._partition_updates[p].tgt_group = g
3065
3066 for g in src_groups:
3067 for p in shlex.split(source_info_dict.get(
3068 "super_%s_partition_list" % g, "").strip()):
3069 assert p in self._partition_updates, \
3070 "{} is in source super_{}_partition_list but no BlockDifference " \
3071 "object is provided.".format(p, g)
3072 self._partition_updates[p].src_group = g
3073
Yifan Hong45433e42019-01-18 13:55:25 -08003074 target_dynamic_partitions = set(shlex.split(info_dict.get(
3075 "dynamic_partition_list", "").strip()))
3076 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3077 if u.tgt_size)
3078 assert block_diffs_with_target == target_dynamic_partitions, \
3079 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3080 list(target_dynamic_partitions), list(block_diffs_with_target))
3081
3082 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3083 "dynamic_partition_list", "").strip()))
3084 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3085 if u.src_size)
3086 assert block_diffs_with_source == source_dynamic_partitions, \
3087 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3088 list(source_dynamic_partitions), list(block_diffs_with_source))
3089
Yifan Hong10c530d2018-12-27 17:34:18 -08003090 if self._partition_updates:
3091 logger.info("Updating dynamic partitions %s",
3092 self._partition_updates.keys())
3093
Yifan Hong79997e52019-01-23 16:56:19 -08003094 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003095
3096 for g in tgt_groups:
3097 self._group_updates[g] = DynamicGroupUpdate()
3098 self._group_updates[g].tgt_size = int(info_dict.get(
3099 "super_%s_group_size" % g, "0").strip())
3100
3101 for g in src_groups:
3102 if g not in self._group_updates:
3103 self._group_updates[g] = DynamicGroupUpdate()
3104 self._group_updates[g].src_size = int(source_info_dict.get(
3105 "super_%s_group_size" % g, "0").strip())
3106
3107 self._Compute()
3108
3109 def WriteScript(self, script, output_zip, write_verify_script=False):
3110 script.Comment('--- Start patching dynamic partitions ---')
3111 for p, u in self._partition_updates.items():
3112 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3113 script.Comment('Patch partition %s' % p)
3114 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3115 write_verify_script=False)
3116
3117 op_list_path = MakeTempFile()
3118 with open(op_list_path, 'w') as f:
3119 for line in self._op_list:
3120 f.write('{}\n'.format(line))
3121
3122 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3123
3124 script.Comment('Update dynamic partition metadata')
3125 script.AppendExtra('assert(update_dynamic_partitions('
3126 'package_extract_file("dynamic_partitions_op_list")));')
3127
3128 if write_verify_script:
3129 for p, u in self._partition_updates.items():
3130 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3131 u.block_difference.WritePostInstallVerifyScript(script)
3132 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3133
3134 for p, u in self._partition_updates.items():
3135 if u.tgt_size and u.src_size <= u.tgt_size:
3136 script.Comment('Patch partition %s' % p)
3137 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3138 write_verify_script=write_verify_script)
3139 if write_verify_script:
3140 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3141
3142 script.Comment('--- End patching dynamic partitions ---')
3143
3144 def _Compute(self):
3145 self._op_list = list()
3146
3147 def append(line):
3148 self._op_list.append(line)
3149
3150 def comment(line):
3151 self._op_list.append("# %s" % line)
3152
3153 if self._remove_all_before_apply:
3154 comment('Remove all existing dynamic partitions and groups before '
3155 'applying full OTA')
3156 append('remove_all_groups')
3157
3158 for p, u in self._partition_updates.items():
3159 if u.src_group and not u.tgt_group:
3160 append('remove %s' % p)
3161
3162 for p, u in self._partition_updates.items():
3163 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3164 comment('Move partition %s from %s to default' % (p, u.src_group))
3165 append('move %s default' % p)
3166
3167 for p, u in self._partition_updates.items():
3168 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3169 comment('Shrink partition %s from %d to %d' %
3170 (p, u.src_size, u.tgt_size))
3171 append('resize %s %s' % (p, u.tgt_size))
3172
3173 for g, u in self._group_updates.items():
3174 if u.src_size is not None and u.tgt_size is None:
3175 append('remove_group %s' % g)
3176 if (u.src_size is not None and u.tgt_size is not None and
3177 u.src_size > u.tgt_size):
3178 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3179 append('resize_group %s %d' % (g, u.tgt_size))
3180
3181 for g, u in self._group_updates.items():
3182 if u.src_size is None and u.tgt_size is not None:
3183 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3184 append('add_group %s %d' % (g, u.tgt_size))
3185 if (u.src_size is not None and u.tgt_size is not None and
3186 u.src_size < u.tgt_size):
3187 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3188 append('resize_group %s %d' % (g, u.tgt_size))
3189
3190 for p, u in self._partition_updates.items():
3191 if u.tgt_group and not u.src_group:
3192 comment('Add partition %s to group %s' % (p, u.tgt_group))
3193 append('add %s %s' % (p, u.tgt_group))
3194
3195 for p, u in self._partition_updates.items():
3196 if u.tgt_size and u.src_size < u.tgt_size:
3197 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3198 append('resize %s %d' % (p, u.tgt_size))
3199
3200 for p, u in self._partition_updates.items():
3201 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3202 comment('Move partition %s from default to %s' %
3203 (p, u.tgt_group))
3204 append('move %s %s' % (p, u.tgt_group))