blob: 7f40374ed6cf1f5c63eab59475dc1d779c3804f1 [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))
434 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))
439
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
445 try:
446 return self.info_dict.get("build.prop", {})[prop]
447 except KeyError:
448 raise ExternalError("couldn't find %s in build.prop" % (prop,))
449
450 def _ResolveRoProductBuildProp(self, prop):
451 """Resolves the inquired ro.product.* build property"""
452 prop_val = self.info_dict.get("build.prop", {}).get(prop)
453 if prop_val:
454 return prop_val
455
Steven Laver8e2086e2020-04-27 16:26:31 -0700456 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tao Bao1c320f82019-10-04 23:25:12 -0700457 source_order_val = self.info_dict.get("build.prop", {}).get(
458 "ro.product.property_source_order")
459 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
469 for source in source_order:
470 source_prop = prop.replace(
471 "ro.product", "ro.product.{}".format(source), 1)
472 prop_val = self.info_dict.get(
473 "{}.build.prop".format(source), {}).get(source_prop)
474 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.
482 android_codename = self.info_dict.get("build.prop", {}).get(
483 "ro.build.version.codename")
484 if android_codename == "REL":
485 android_version = self.info_dict.get("build.prop", {}).get(
486 "ro.build.version.release")
487 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):
Dan Albert8b72aef2015-03-23 19:13:21 -0700606 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800607 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800608 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700609 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800610 try:
611 with open(path) as f:
612 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700613 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800614 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)
678 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700679 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800680 # 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
Tao Baobcd1d162017-08-26 13:10:26 -0700699def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700700 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700701 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700702 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700703 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700704 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700705 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700706
Tao Baod1de6f32017-03-01 16:38:48 -0800707
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")
1151 else:
1152 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001153 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001154 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001155
Tao Bao76def242017-11-21 09:25:31 -08001156 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001157 if args and args.strip():
1158 cmd.extend(shlex.split(args))
1159
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001160 if has_ramdisk:
1161 cmd.extend(["--ramdisk", ramdisk_img.name])
1162
Tao Baod95e9fd2015-03-29 23:07:41 -07001163 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001164 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001165 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001166 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001167 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001168 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001169
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001170 if partition_name == "recovery":
1171 if info_dict.get("include_recovery_dtbo") == "true":
1172 fn = os.path.join(sourcedir, "recovery_dtbo")
1173 cmd.extend(["--recovery_dtbo", fn])
1174 if info_dict.get("include_recovery_acpio") == "true":
1175 fn = os.path.join(sourcedir, "recovery_acpio")
1176 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001177
Tao Bao986ee862018-10-04 15:46:16 -07001178 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001179
Tao Bao76def242017-11-21 09:25:31 -08001180 if (info_dict.get("boot_signer") == "true" and
1181 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001182 # Hard-code the path as "/boot" for two-step special recovery image (which
1183 # will be loaded into /boot during the two-step OTA).
1184 if two_step_image:
1185 path = "/boot"
1186 else:
Tao Baobf70c312017-07-11 17:27:55 -07001187 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001188 cmd = [OPTIONS.boot_signer_path]
1189 cmd.extend(OPTIONS.boot_signer_args)
1190 cmd.extend([path, img.name,
1191 info_dict["verity_key"] + ".pk8",
1192 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001193 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001194
Tao Baod95e9fd2015-03-29 23:07:41 -07001195 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001196 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001197 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001198 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001199 # We have switched from the prebuilt futility binary to using the tool
1200 # (futility-host) built from the source. Override the setting in the old
1201 # TF.zip.
1202 futility = info_dict["futility"]
1203 if futility.startswith("prebuilts/"):
1204 futility = "futility-host"
1205 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001206 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001207 info_dict["vboot_key"] + ".vbprivk",
1208 info_dict["vboot_subkey"] + ".vbprivk",
1209 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001210 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001211 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001212
Tao Baof3282b42015-04-01 11:21:55 -07001213 # Clean up the temp files.
1214 img_unsigned.close()
1215 img_keyblock.close()
1216
David Zeuthen8fecb282017-12-01 16:24:01 -05001217 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001218 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001219 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001220 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001221 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001222 "--partition_size", str(part_size), "--partition_name",
1223 partition_name]
1224 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001225 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001226 if args and args.strip():
1227 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001228 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001229
1230 img.seek(os.SEEK_SET, 0)
1231 data = img.read()
1232
1233 if has_ramdisk:
1234 ramdisk_img.close()
1235 img.close()
1236
1237 return data
1238
1239
Doug Zongkerd5131602012-08-02 14:46:42 -07001240def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001241 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001242 """Return a File object with the desired bootable image.
1243
1244 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1245 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1246 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001247
Doug Zongker55d93282011-01-25 17:03:34 -08001248 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1249 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001250 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001251 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001252
1253 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1254 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001255 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001256 return File.FromLocalFile(name, prebuilt_path)
1257
Tao Bao32fcdab2018-10-12 10:30:39 -07001258 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001259
1260 if info_dict is None:
1261 info_dict = OPTIONS.info_dict
1262
1263 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001264 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1265 # for recovery.
1266 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1267 prebuilt_name != "boot.img" or
1268 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001269
Doug Zongker6f1d0312014-08-22 08:07:12 -07001270 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001271 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001272 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001273 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001274 if data:
1275 return File(name, data)
1276 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001277
Doug Zongkereef39442009-04-02 12:14:19 -07001278
Steve Mucklee1b10862019-07-10 10:49:37 -07001279def _BuildVendorBootImage(sourcedir, info_dict=None):
1280 """Build a vendor boot image from the specified sourcedir.
1281
1282 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1283 turn them into a vendor boot image.
1284
1285 Return the image data, or None if sourcedir does not appear to contains files
1286 for building the requested image.
1287 """
1288
1289 if info_dict is None:
1290 info_dict = OPTIONS.info_dict
1291
1292 img = tempfile.NamedTemporaryFile()
1293
1294 ramdisk_img = _MakeRamdisk(sourcedir)
1295
1296 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1297 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1298
1299 cmd = [mkbootimg]
1300
1301 fn = os.path.join(sourcedir, "dtb")
1302 if os.access(fn, os.F_OK):
1303 cmd.append("--dtb")
1304 cmd.append(fn)
1305
1306 fn = os.path.join(sourcedir, "vendor_cmdline")
1307 if os.access(fn, os.F_OK):
1308 cmd.append("--vendor_cmdline")
1309 cmd.append(open(fn).read().rstrip("\n"))
1310
1311 fn = os.path.join(sourcedir, "base")
1312 if os.access(fn, os.F_OK):
1313 cmd.append("--base")
1314 cmd.append(open(fn).read().rstrip("\n"))
1315
1316 fn = os.path.join(sourcedir, "pagesize")
1317 if os.access(fn, os.F_OK):
1318 cmd.append("--pagesize")
1319 cmd.append(open(fn).read().rstrip("\n"))
1320
1321 args = info_dict.get("mkbootimg_args")
1322 if args and args.strip():
1323 cmd.extend(shlex.split(args))
1324
1325 args = info_dict.get("mkbootimg_version_args")
1326 if args and args.strip():
1327 cmd.extend(shlex.split(args))
1328
1329 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1330 cmd.extend(["--vendor_boot", img.name])
1331
1332 RunAndCheckOutput(cmd)
1333
1334 # AVB: if enabled, calculate and add hash.
1335 if info_dict.get("avb_enable") == "true":
1336 avbtool = info_dict["avb_avbtool"]
1337 part_size = info_dict["vendor_boot_size"]
1338 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001339 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001340 AppendAVBSigningArgs(cmd, "vendor_boot")
1341 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1342 if args and args.strip():
1343 cmd.extend(shlex.split(args))
1344 RunAndCheckOutput(cmd)
1345
1346 img.seek(os.SEEK_SET, 0)
1347 data = img.read()
1348
1349 ramdisk_img.close()
1350 img.close()
1351
1352 return data
1353
1354
1355def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1356 info_dict=None):
1357 """Return a File object with the desired vendor boot image.
1358
1359 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1360 the source files in 'unpack_dir'/'tree_subdir'."""
1361
1362 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1363 if os.path.exists(prebuilt_path):
1364 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1365 return File.FromLocalFile(name, prebuilt_path)
1366
1367 logger.info("building image from target_files %s...", tree_subdir)
1368
1369 if info_dict is None:
1370 info_dict = OPTIONS.info_dict
1371
1372 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1373 if data:
1374 return File(name, data)
1375 return None
1376
1377
Narayan Kamatha07bf042017-08-14 14:49:21 +01001378def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001379 """Gunzips the given gzip compressed file to a given output file."""
1380 with gzip.open(in_filename, "rb") as in_file, \
1381 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001382 shutil.copyfileobj(in_file, out_file)
1383
1384
Tao Bao0ff15de2019-03-20 11:26:06 -07001385def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001386 """Unzips the archive to the given directory.
1387
1388 Args:
1389 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001390 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001391 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1392 archvie. Non-matching patterns will be filtered out. If there's no match
1393 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001394 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001395 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001396 if patterns is not None:
1397 # Filter out non-matching patterns. unzip will complain otherwise.
1398 with zipfile.ZipFile(filename) as input_zip:
1399 names = input_zip.namelist()
1400 filtered = [
1401 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1402
1403 # There isn't any matching files. Don't unzip anything.
1404 if not filtered:
1405 return
1406 cmd.extend(filtered)
1407
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001408 RunAndCheckOutput(cmd)
1409
1410
Doug Zongker75f17362009-12-08 13:46:44 -08001411def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001412 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001413
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001414 Args:
1415 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1416 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1417
1418 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1419 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001420
Tao Bao1c830bf2017-12-25 10:43:47 -08001421 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001422 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001423 """
Doug Zongkereef39442009-04-02 12:14:19 -07001424
Tao Bao1c830bf2017-12-25 10:43:47 -08001425 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001426 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1427 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001428 UnzipToDir(m.group(1), tmp, pattern)
1429 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001430 filename = m.group(1)
1431 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001432 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001433
Tao Baodba59ee2018-01-09 13:21:02 -08001434 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001435
1436
Yifan Hong8a66a712019-04-04 15:37:57 -07001437def GetUserImage(which, tmpdir, input_zip,
1438 info_dict=None,
1439 allow_shared_blocks=None,
1440 hashtree_info_generator=None,
1441 reset_file_map=False):
1442 """Returns an Image object suitable for passing to BlockImageDiff.
1443
1444 This function loads the specified image from the given path. If the specified
1445 image is sparse, it also performs additional processing for OTA purpose. For
1446 example, it always adds block 0 to clobbered blocks list. It also detects
1447 files that cannot be reconstructed from the block list, for whom we should
1448 avoid applying imgdiff.
1449
1450 Args:
1451 which: The partition name.
1452 tmpdir: The directory that contains the prebuilt image and block map file.
1453 input_zip: The target-files ZIP archive.
1454 info_dict: The dict to be looked up for relevant info.
1455 allow_shared_blocks: If image is sparse, whether having shared blocks is
1456 allowed. If none, it is looked up from info_dict.
1457 hashtree_info_generator: If present and image is sparse, generates the
1458 hashtree_info for this sparse image.
1459 reset_file_map: If true and image is sparse, reset file map before returning
1460 the image.
1461 Returns:
1462 A Image object. If it is a sparse image and reset_file_map is False, the
1463 image will have file_map info loaded.
1464 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001465 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001466 info_dict = LoadInfoDict(input_zip)
1467
1468 is_sparse = info_dict.get("extfs_sparse_flag")
1469
1470 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1471 # shared blocks (i.e. some blocks will show up in multiple files' block
1472 # list). We can only allocate such shared blocks to the first "owner", and
1473 # disable imgdiff for all later occurrences.
1474 if allow_shared_blocks is None:
1475 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1476
1477 if is_sparse:
1478 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1479 hashtree_info_generator)
1480 if reset_file_map:
1481 img.ResetFileMap()
1482 return img
1483 else:
1484 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1485
1486
1487def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1488 """Returns a Image object suitable for passing to BlockImageDiff.
1489
1490 This function loads the specified non-sparse image from the given path.
1491
1492 Args:
1493 which: The partition name.
1494 tmpdir: The directory that contains the prebuilt image and block map file.
1495 Returns:
1496 A Image object.
1497 """
1498 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1499 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1500
1501 # The image and map files must have been created prior to calling
1502 # ota_from_target_files.py (since LMP).
1503 assert os.path.exists(path) and os.path.exists(mappath)
1504
Tianjie Xu41976c72019-07-03 13:57:01 -07001505 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1506
Yifan Hong8a66a712019-04-04 15:37:57 -07001507
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001508def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1509 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001510 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1511
1512 This function loads the specified sparse image from the given path, and
1513 performs additional processing for OTA purpose. For example, it always adds
1514 block 0 to clobbered blocks list. It also detects files that cannot be
1515 reconstructed from the block list, for whom we should avoid applying imgdiff.
1516
1517 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001518 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001519 tmpdir: The directory that contains the prebuilt image and block map file.
1520 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001521 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001522 hashtree_info_generator: If present, generates the hashtree_info for this
1523 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001524 Returns:
1525 A SparseImage object, with file_map info loaded.
1526 """
Tao Baoc765cca2018-01-31 17:32:40 -08001527 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1528 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1529
1530 # The image and map files must have been created prior to calling
1531 # ota_from_target_files.py (since LMP).
1532 assert os.path.exists(path) and os.path.exists(mappath)
1533
1534 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1535 # it to clobbered_blocks so that it will be written to the target
1536 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1537 clobbered_blocks = "0"
1538
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001539 image = sparse_img.SparseImage(
1540 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1541 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001542
1543 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1544 # if they contain all zeros. We can't reconstruct such a file from its block
1545 # list. Tag such entries accordingly. (Bug: 65213616)
1546 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001547 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001548 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001549 continue
1550
Tom Cherryd14b8952018-08-09 14:26:00 -07001551 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1552 # filename listed in system.map may contain an additional leading slash
1553 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1554 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001555 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001556
Tom Cherryd14b8952018-08-09 14:26:00 -07001557 # Special handling another case, where files not under /system
1558 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001559 if which == 'system' and not arcname.startswith('SYSTEM'):
1560 arcname = 'ROOT/' + arcname
1561
1562 assert arcname in input_zip.namelist(), \
1563 "Failed to find the ZIP entry for {}".format(entry)
1564
Tao Baoc765cca2018-01-31 17:32:40 -08001565 info = input_zip.getinfo(arcname)
1566 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001567
1568 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001569 # image, check the original block list to determine its completeness. Note
1570 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001571 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001572 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001573
Tao Baoc765cca2018-01-31 17:32:40 -08001574 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1575 ranges.extra['incomplete'] = True
1576
1577 return image
1578
1579
Doug Zongkereef39442009-04-02 12:14:19 -07001580def GetKeyPasswords(keylist):
1581 """Given a list of keys, prompt the user to enter passwords for
1582 those which require them. Return a {key: password} dict. password
1583 will be None if the key has no password."""
1584
Doug Zongker8ce7c252009-05-22 13:34:54 -07001585 no_passwords = []
1586 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001587 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001588 devnull = open("/dev/null", "w+b")
1589 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001590 # We don't need a password for things that aren't really keys.
1591 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001592 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001593 continue
1594
T.R. Fullhart37e10522013-03-18 10:31:26 -07001595 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001596 "-inform", "DER", "-nocrypt"],
1597 stdin=devnull.fileno(),
1598 stdout=devnull.fileno(),
1599 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001600 p.communicate()
1601 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001602 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001603 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001604 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001605 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1606 "-inform", "DER", "-passin", "pass:"],
1607 stdin=devnull.fileno(),
1608 stdout=devnull.fileno(),
1609 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001610 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001611 if p.returncode == 0:
1612 # Encrypted key with empty string as password.
1613 key_passwords[k] = ''
1614 elif stderr.startswith('Error decrypting key'):
1615 # Definitely encrypted key.
1616 # It would have said "Error reading key" if it didn't parse correctly.
1617 need_passwords.append(k)
1618 else:
1619 # Potentially, a type of key that openssl doesn't understand.
1620 # We'll let the routines in signapk.jar handle it.
1621 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001622 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001623
T.R. Fullhart37e10522013-03-18 10:31:26 -07001624 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001625 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001626 return key_passwords
1627
1628
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001629def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001630 """Gets the minSdkVersion declared in the APK.
1631
changho.shin0f125362019-07-08 10:59:00 +09001632 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001633 This can be both a decimal number (API Level) or a codename.
1634
1635 Args:
1636 apk_name: The APK filename.
1637
1638 Returns:
1639 The parsed SDK version string.
1640
1641 Raises:
1642 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001643 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001644 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001645 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001646 stderr=subprocess.PIPE)
1647 stdoutdata, stderrdata = proc.communicate()
1648 if proc.returncode != 0:
1649 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001650 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001651 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001652
Tao Baof47bf0f2018-03-21 23:28:51 -07001653 for line in stdoutdata.split("\n"):
1654 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001655 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1656 if m:
1657 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001658 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001659
1660
1661def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001662 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001663
Tao Baof47bf0f2018-03-21 23:28:51 -07001664 If minSdkVersion is set to a codename, it is translated to a number using the
1665 provided map.
1666
1667 Args:
1668 apk_name: The APK filename.
1669
1670 Returns:
1671 The parsed SDK version number.
1672
1673 Raises:
1674 ExternalError: On failing to get the min SDK version number.
1675 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001676 version = GetMinSdkVersion(apk_name)
1677 try:
1678 return int(version)
1679 except ValueError:
1680 # Not a decimal number. Codename?
1681 if version in codename_to_api_level_map:
1682 return codename_to_api_level_map[version]
1683 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001684 raise ExternalError(
1685 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1686 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001687
1688
1689def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001690 codename_to_api_level_map=None, whole_file=False,
1691 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001692 """Sign the input_name zip/jar/apk, producing output_name. Use the
1693 given key and password (the latter may be None if the key does not
1694 have a password.
1695
Doug Zongker951495f2009-08-14 12:44:19 -07001696 If whole_file is true, use the "-w" option to SignApk to embed a
1697 signature that covers the whole file in the archive comment of the
1698 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001699
1700 min_api_level is the API Level (int) of the oldest platform this file may end
1701 up on. If not specified for an APK, the API Level is obtained by interpreting
1702 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1703
1704 codename_to_api_level_map is needed to translate the codename which may be
1705 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001706
1707 Caller may optionally specify extra args to be passed to SignApk, which
1708 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001709 """
Tao Bao76def242017-11-21 09:25:31 -08001710 if codename_to_api_level_map is None:
1711 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001712 if extra_signapk_args is None:
1713 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001714
Alex Klyubin9667b182015-12-10 13:38:50 -08001715 java_library_path = os.path.join(
1716 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1717
Tao Baoe95540e2016-11-08 12:08:53 -08001718 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1719 ["-Djava.library.path=" + java_library_path,
1720 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001721 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001722 if whole_file:
1723 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001724
1725 min_sdk_version = min_api_level
1726 if min_sdk_version is None:
1727 if not whole_file:
1728 min_sdk_version = GetMinSdkVersionInt(
1729 input_name, codename_to_api_level_map)
1730 if min_sdk_version is not None:
1731 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1732
T.R. Fullhart37e10522013-03-18 10:31:26 -07001733 cmd.extend([key + OPTIONS.public_key_suffix,
1734 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001735 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001736
Tao Bao73dd4f42018-10-04 16:25:33 -07001737 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001738 if password is not None:
1739 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001740 stdoutdata, _ = proc.communicate(password)
1741 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001742 raise ExternalError(
1743 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001744 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001745
Doug Zongkereef39442009-04-02 12:14:19 -07001746
Doug Zongker37974732010-09-16 17:44:38 -07001747def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001748 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001749
Tao Bao9dd909e2017-11-14 11:27:32 -08001750 For non-AVB images, raise exception if the data is too big. Print a warning
1751 if the data is nearing the maximum size.
1752
1753 For AVB images, the actual image size should be identical to the limit.
1754
1755 Args:
1756 data: A string that contains all the data for the partition.
1757 target: The partition name. The ".img" suffix is optional.
1758 info_dict: The dict to be looked up for relevant info.
1759 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001760 if target.endswith(".img"):
1761 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001762 mount_point = "/" + target
1763
Ying Wangf8824af2014-06-03 14:07:27 -07001764 fs_type = None
1765 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001766 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001767 if mount_point == "/userdata":
1768 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001769 p = info_dict["fstab"][mount_point]
1770 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001771 device = p.device
1772 if "/" in device:
1773 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001774 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001775 if not fs_type or not limit:
1776 return
Doug Zongkereef39442009-04-02 12:14:19 -07001777
Andrew Boie0f9aec82012-02-14 09:32:52 -08001778 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001779 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1780 # path.
1781 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1782 if size != limit:
1783 raise ExternalError(
1784 "Mismatching image size for %s: expected %d actual %d" % (
1785 target, limit, size))
1786 else:
1787 pct = float(size) * 100.0 / limit
1788 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1789 if pct >= 99.0:
1790 raise ExternalError(msg)
1791 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001792 logger.warning("\n WARNING: %s\n", msg)
1793 else:
1794 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001795
1796
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001797def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001798 """Parses the APK certs info from a given target-files zip.
1799
1800 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1801 tuple with the following elements: (1) a dictionary that maps packages to
1802 certs (based on the "certificate" and "private_key" attributes in the file;
1803 (2) a string representing the extension of compressed APKs in the target files
1804 (e.g ".gz", ".bro").
1805
1806 Args:
1807 tf_zip: The input target_files ZipFile (already open).
1808
1809 Returns:
1810 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1811 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1812 no compressed APKs.
1813 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001814 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001815 compressed_extension = None
1816
Tao Bao0f990332017-09-08 19:02:54 -07001817 # META/apkcerts.txt contains the info for _all_ the packages known at build
1818 # time. Filter out the ones that are not installed.
1819 installed_files = set()
1820 for name in tf_zip.namelist():
1821 basename = os.path.basename(name)
1822 if basename:
1823 installed_files.add(basename)
1824
Tao Baoda30cfa2017-12-01 16:19:46 -08001825 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001826 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001827 if not line:
1828 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001829 m = re.match(
1830 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001831 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1832 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001833 line)
1834 if not m:
1835 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001836
Tao Bao818ddf52018-01-05 11:17:34 -08001837 matches = m.groupdict()
1838 cert = matches["CERT"]
1839 privkey = matches["PRIVKEY"]
1840 name = matches["NAME"]
1841 this_compressed_extension = matches["COMPRESSED"]
1842
1843 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1844 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1845 if cert in SPECIAL_CERT_STRINGS and not privkey:
1846 certmap[name] = cert
1847 elif (cert.endswith(OPTIONS.public_key_suffix) and
1848 privkey.endswith(OPTIONS.private_key_suffix) and
1849 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1850 certmap[name] = cert[:-public_key_suffix_len]
1851 else:
1852 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1853
1854 if not this_compressed_extension:
1855 continue
1856
1857 # Only count the installed files.
1858 filename = name + '.' + this_compressed_extension
1859 if filename not in installed_files:
1860 continue
1861
1862 # Make sure that all the values in the compression map have the same
1863 # extension. We don't support multiple compression methods in the same
1864 # system image.
1865 if compressed_extension:
1866 if this_compressed_extension != compressed_extension:
1867 raise ValueError(
1868 "Multiple compressed extensions: {} vs {}".format(
1869 compressed_extension, this_compressed_extension))
1870 else:
1871 compressed_extension = this_compressed_extension
1872
1873 return (certmap,
1874 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001875
1876
Doug Zongkereef39442009-04-02 12:14:19 -07001877COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001878Global options
1879
1880 -p (--path) <dir>
1881 Prepend <dir>/bin to the list of places to search for binaries run by this
1882 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001883
Doug Zongker05d3dea2009-06-22 11:32:31 -07001884 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001885 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001886
Tao Bao30df8b42018-04-23 15:32:53 -07001887 -x (--extra) <key=value>
1888 Add a key/value pair to the 'extras' dict, which device-specific extension
1889 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001890
Doug Zongkereef39442009-04-02 12:14:19 -07001891 -v (--verbose)
1892 Show command lines being executed.
1893
1894 -h (--help)
1895 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001896
1897 --logfile <file>
1898 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001899"""
1900
1901def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001902 print(docstring.rstrip("\n"))
1903 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001904
1905
1906def ParseOptions(argv,
1907 docstring,
1908 extra_opts="", extra_long_opts=(),
1909 extra_option_handler=None):
1910 """Parse the options in argv and return any arguments that aren't
1911 flags. docstring is the calling module's docstring, to be displayed
1912 for errors and -h. extra_opts and extra_long_opts are for flags
1913 defined by the caller, which are processed by passing them to
1914 extra_option_handler."""
1915
1916 try:
1917 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001918 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001919 ["help", "verbose", "path=", "signapk_path=",
1920 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08001921 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001922 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1923 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07001924 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
1925 "aftl_key_path=", "aftl_manufacturer_key_path=",
1926 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001927 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001928 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001929 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001930 sys.exit(2)
1931
Doug Zongkereef39442009-04-02 12:14:19 -07001932 for o, a in opts:
1933 if o in ("-h", "--help"):
1934 Usage(docstring)
1935 sys.exit()
1936 elif o in ("-v", "--verbose"):
1937 OPTIONS.verbose = True
1938 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001939 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001940 elif o in ("--signapk_path",):
1941 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001942 elif o in ("--signapk_shared_library_path",):
1943 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001944 elif o in ("--extra_signapk_args",):
1945 OPTIONS.extra_signapk_args = shlex.split(a)
1946 elif o in ("--java_path",):
1947 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001948 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001949 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08001950 elif o in ("--android_jar_path",):
1951 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001952 elif o in ("--public_key_suffix",):
1953 OPTIONS.public_key_suffix = a
1954 elif o in ("--private_key_suffix",):
1955 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001956 elif o in ("--boot_signer_path",):
1957 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001958 elif o in ("--boot_signer_args",):
1959 OPTIONS.boot_signer_args = shlex.split(a)
1960 elif o in ("--verity_signer_path",):
1961 OPTIONS.verity_signer_path = a
1962 elif o in ("--verity_signer_args",):
1963 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07001964 elif o in ("--aftl_tool_path",):
1965 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08001966 elif o in ("--aftl_server",):
1967 OPTIONS.aftl_server = a
1968 elif o in ("--aftl_key_path",):
1969 OPTIONS.aftl_key_path = a
1970 elif o in ("--aftl_manufacturer_key_path",):
1971 OPTIONS.aftl_manufacturer_key_path = a
1972 elif o in ("--aftl_signer_helper",):
1973 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07001974 elif o in ("-s", "--device_specific"):
1975 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001976 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001977 key, value = a.split("=", 1)
1978 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001979 elif o in ("--logfile",):
1980 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001981 else:
1982 if extra_option_handler is None or not extra_option_handler(o, a):
1983 assert False, "unknown option \"%s\"" % (o,)
1984
Doug Zongker85448772014-09-09 14:59:20 -07001985 if OPTIONS.search_path:
1986 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1987 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001988
1989 return args
1990
1991
Tao Bao4c851b12016-09-19 13:54:38 -07001992def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001993 """Make a temp file and add it to the list of things to be deleted
1994 when Cleanup() is called. Return the filename."""
1995 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1996 os.close(fd)
1997 OPTIONS.tempfiles.append(fn)
1998 return fn
1999
2000
Tao Bao1c830bf2017-12-25 10:43:47 -08002001def MakeTempDir(prefix='tmp', suffix=''):
2002 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2003
2004 Returns:
2005 The absolute pathname of the new directory.
2006 """
2007 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2008 OPTIONS.tempfiles.append(dir_name)
2009 return dir_name
2010
2011
Doug Zongkereef39442009-04-02 12:14:19 -07002012def Cleanup():
2013 for i in OPTIONS.tempfiles:
2014 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002015 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002016 else:
2017 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002018 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002019
2020
2021class PasswordManager(object):
2022 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002023 self.editor = os.getenv("EDITOR")
2024 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002025
2026 def GetPasswords(self, items):
2027 """Get passwords corresponding to each string in 'items',
2028 returning a dict. (The dict may have keys in addition to the
2029 values in 'items'.)
2030
2031 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2032 user edit that file to add more needed passwords. If no editor is
2033 available, or $ANDROID_PW_FILE isn't define, prompts the user
2034 interactively in the ordinary way.
2035 """
2036
2037 current = self.ReadFile()
2038
2039 first = True
2040 while True:
2041 missing = []
2042 for i in items:
2043 if i not in current or not current[i]:
2044 missing.append(i)
2045 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002046 if not missing:
2047 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002048
2049 for i in missing:
2050 current[i] = ""
2051
2052 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002053 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002054 if sys.version_info[0] >= 3:
2055 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002056 answer = raw_input("try to edit again? [y]> ").strip()
2057 if answer and answer[0] not in 'yY':
2058 raise RuntimeError("key passwords unavailable")
2059 first = False
2060
2061 current = self.UpdateAndReadFile(current)
2062
Dan Albert8b72aef2015-03-23 19:13:21 -07002063 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002064 """Prompt the user to enter a value (password) for each key in
2065 'current' whose value is fales. Returns a new dict with all the
2066 values.
2067 """
2068 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002069 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002070 if v:
2071 result[k] = v
2072 else:
2073 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002074 result[k] = getpass.getpass(
2075 "Enter password for %s key> " % k).strip()
2076 if result[k]:
2077 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002078 return result
2079
2080 def UpdateAndReadFile(self, current):
2081 if not self.editor or not self.pwfile:
2082 return self.PromptResult(current)
2083
2084 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002085 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002086 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2087 f.write("# (Additional spaces are harmless.)\n\n")
2088
2089 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002090 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002091 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002092 f.write("[[[ %s ]]] %s\n" % (v, k))
2093 if not v and first_line is None:
2094 # position cursor on first line with no password.
2095 first_line = i + 4
2096 f.close()
2097
Tao Bao986ee862018-10-04 15:46:16 -07002098 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002099
2100 return self.ReadFile()
2101
2102 def ReadFile(self):
2103 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002104 if self.pwfile is None:
2105 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002106 try:
2107 f = open(self.pwfile, "r")
2108 for line in f:
2109 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002110 if not line or line[0] == '#':
2111 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002112 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2113 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002114 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002115 else:
2116 result[m.group(2)] = m.group(1)
2117 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002118 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002119 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002120 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002121 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002122
2123
Dan Albert8e0178d2015-01-27 15:53:15 -08002124def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2125 compress_type=None):
2126 import datetime
2127
2128 # http://b/18015246
2129 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2130 # for files larger than 2GiB. We can work around this by adjusting their
2131 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2132 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2133 # it isn't clear to me exactly what circumstances cause this).
2134 # `zipfile.write()` must be used directly to work around this.
2135 #
2136 # This mess can be avoided if we port to python3.
2137 saved_zip64_limit = zipfile.ZIP64_LIMIT
2138 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2139
2140 if compress_type is None:
2141 compress_type = zip_file.compression
2142 if arcname is None:
2143 arcname = filename
2144
2145 saved_stat = os.stat(filename)
2146
2147 try:
2148 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2149 # file to be zipped and reset it when we're done.
2150 os.chmod(filename, perms)
2151
2152 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002153 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2154 # intentional. zip stores datetimes in local time without a time zone
2155 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2156 # in the zip archive.
2157 local_epoch = datetime.datetime.fromtimestamp(0)
2158 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002159 os.utime(filename, (timestamp, timestamp))
2160
2161 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2162 finally:
2163 os.chmod(filename, saved_stat.st_mode)
2164 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2165 zipfile.ZIP64_LIMIT = saved_zip64_limit
2166
2167
Tao Bao58c1b962015-05-20 09:32:18 -07002168def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002169 compress_type=None):
2170 """Wrap zipfile.writestr() function to work around the zip64 limit.
2171
2172 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2173 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2174 when calling crc32(bytes).
2175
2176 But it still works fine to write a shorter string into a large zip file.
2177 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2178 when we know the string won't be too long.
2179 """
2180
2181 saved_zip64_limit = zipfile.ZIP64_LIMIT
2182 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2183
2184 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2185 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002186 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002187 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002188 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002189 else:
Tao Baof3282b42015-04-01 11:21:55 -07002190 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002191 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2192 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2193 # such a case (since
2194 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2195 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2196 # permission bits. We follow the logic in Python 3 to get consistent
2197 # behavior between using the two versions.
2198 if not zinfo.external_attr:
2199 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002200
2201 # If compress_type is given, it overrides the value in zinfo.
2202 if compress_type is not None:
2203 zinfo.compress_type = compress_type
2204
Tao Bao58c1b962015-05-20 09:32:18 -07002205 # If perms is given, it has a priority.
2206 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002207 # If perms doesn't set the file type, mark it as a regular file.
2208 if perms & 0o770000 == 0:
2209 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002210 zinfo.external_attr = perms << 16
2211
Tao Baof3282b42015-04-01 11:21:55 -07002212 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002213 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2214
Dan Albert8b72aef2015-03-23 19:13:21 -07002215 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002216 zipfile.ZIP64_LIMIT = saved_zip64_limit
2217
2218
Tao Bao89d7ab22017-12-14 17:05:33 -08002219def ZipDelete(zip_filename, entries):
2220 """Deletes entries from a ZIP file.
2221
2222 Since deleting entries from a ZIP file is not supported, it shells out to
2223 'zip -d'.
2224
2225 Args:
2226 zip_filename: The name of the ZIP file.
2227 entries: The name of the entry, or the list of names to be deleted.
2228
2229 Raises:
2230 AssertionError: In case of non-zero return from 'zip'.
2231 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002232 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002233 entries = [entries]
2234 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002235 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002236
2237
Tao Baof3282b42015-04-01 11:21:55 -07002238def ZipClose(zip_file):
2239 # http://b/18015246
2240 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2241 # central directory.
2242 saved_zip64_limit = zipfile.ZIP64_LIMIT
2243 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2244
2245 zip_file.close()
2246
2247 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002248
2249
2250class DeviceSpecificParams(object):
2251 module = None
2252 def __init__(self, **kwargs):
2253 """Keyword arguments to the constructor become attributes of this
2254 object, which is passed to all functions in the device-specific
2255 module."""
Tao Bao38884282019-07-10 22:20:56 -07002256 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002257 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002258 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002259
2260 if self.module is None:
2261 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002262 if not path:
2263 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002264 try:
2265 if os.path.isdir(path):
2266 info = imp.find_module("releasetools", [path])
2267 else:
2268 d, f = os.path.split(path)
2269 b, x = os.path.splitext(f)
2270 if x == ".py":
2271 f = b
2272 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002273 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002274 self.module = imp.load_module("device_specific", *info)
2275 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002276 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002277
2278 def _DoCall(self, function_name, *args, **kwargs):
2279 """Call the named function in the device-specific module, passing
2280 the given args and kwargs. The first argument to the call will be
2281 the DeviceSpecific object itself. If there is no module, or the
2282 module does not define the function, return the value of the
2283 'default' kwarg (which itself defaults to None)."""
2284 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002285 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002286 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2287
2288 def FullOTA_Assertions(self):
2289 """Called after emitting the block of assertions at the top of a
2290 full OTA package. Implementations can add whatever additional
2291 assertions they like."""
2292 return self._DoCall("FullOTA_Assertions")
2293
Doug Zongkere5ff5902012-01-17 10:55:37 -08002294 def FullOTA_InstallBegin(self):
2295 """Called at the start of full OTA installation."""
2296 return self._DoCall("FullOTA_InstallBegin")
2297
Yifan Hong10c530d2018-12-27 17:34:18 -08002298 def FullOTA_GetBlockDifferences(self):
2299 """Called during full OTA installation and verification.
2300 Implementation should return a list of BlockDifference objects describing
2301 the update on each additional partitions.
2302 """
2303 return self._DoCall("FullOTA_GetBlockDifferences")
2304
Doug Zongker05d3dea2009-06-22 11:32:31 -07002305 def FullOTA_InstallEnd(self):
2306 """Called at the end of full OTA installation; typically this is
2307 used to install the image for the device's baseband processor."""
2308 return self._DoCall("FullOTA_InstallEnd")
2309
2310 def IncrementalOTA_Assertions(self):
2311 """Called after emitting the block of assertions at the top of an
2312 incremental OTA package. Implementations can add whatever
2313 additional assertions they like."""
2314 return self._DoCall("IncrementalOTA_Assertions")
2315
Doug Zongkere5ff5902012-01-17 10:55:37 -08002316 def IncrementalOTA_VerifyBegin(self):
2317 """Called at the start of the verification phase of incremental
2318 OTA installation; additional checks can be placed here to abort
2319 the script before any changes are made."""
2320 return self._DoCall("IncrementalOTA_VerifyBegin")
2321
Doug Zongker05d3dea2009-06-22 11:32:31 -07002322 def IncrementalOTA_VerifyEnd(self):
2323 """Called at the end of the verification phase of incremental OTA
2324 installation; additional checks can be placed here to abort the
2325 script before any changes are made."""
2326 return self._DoCall("IncrementalOTA_VerifyEnd")
2327
Doug Zongkere5ff5902012-01-17 10:55:37 -08002328 def IncrementalOTA_InstallBegin(self):
2329 """Called at the start of incremental OTA installation (after
2330 verification is complete)."""
2331 return self._DoCall("IncrementalOTA_InstallBegin")
2332
Yifan Hong10c530d2018-12-27 17:34:18 -08002333 def IncrementalOTA_GetBlockDifferences(self):
2334 """Called during incremental OTA installation and verification.
2335 Implementation should return a list of BlockDifference objects describing
2336 the update on each additional partitions.
2337 """
2338 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2339
Doug Zongker05d3dea2009-06-22 11:32:31 -07002340 def IncrementalOTA_InstallEnd(self):
2341 """Called at the end of incremental OTA installation; typically
2342 this is used to install the image for the device's baseband
2343 processor."""
2344 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002345
Tao Bao9bc6bb22015-11-09 16:58:28 -08002346 def VerifyOTA_Assertions(self):
2347 return self._DoCall("VerifyOTA_Assertions")
2348
Tao Bao76def242017-11-21 09:25:31 -08002349
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002350class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002351 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002352 self.name = name
2353 self.data = data
2354 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002355 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002356 self.sha1 = sha1(data).hexdigest()
2357
2358 @classmethod
2359 def FromLocalFile(cls, name, diskname):
2360 f = open(diskname, "rb")
2361 data = f.read()
2362 f.close()
2363 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002364
2365 def WriteToTemp(self):
2366 t = tempfile.NamedTemporaryFile()
2367 t.write(self.data)
2368 t.flush()
2369 return t
2370
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002371 def WriteToDir(self, d):
2372 with open(os.path.join(d, self.name), "wb") as fp:
2373 fp.write(self.data)
2374
Geremy Condra36bd3652014-02-06 19:45:10 -08002375 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002376 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002377
Tao Bao76def242017-11-21 09:25:31 -08002378
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002379DIFF_PROGRAM_BY_EXT = {
2380 ".gz" : "imgdiff",
2381 ".zip" : ["imgdiff", "-z"],
2382 ".jar" : ["imgdiff", "-z"],
2383 ".apk" : ["imgdiff", "-z"],
2384 ".img" : "imgdiff",
2385 }
2386
Tao Bao76def242017-11-21 09:25:31 -08002387
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002388class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002389 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002390 self.tf = tf
2391 self.sf = sf
2392 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002393 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002394
2395 def ComputePatch(self):
2396 """Compute the patch (as a string of data) needed to turn sf into
2397 tf. Returns the same tuple as GetPatch()."""
2398
2399 tf = self.tf
2400 sf = self.sf
2401
Doug Zongker24cd2802012-08-14 16:36:15 -07002402 if self.diff_program:
2403 diff_program = self.diff_program
2404 else:
2405 ext = os.path.splitext(tf.name)[1]
2406 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002407
2408 ttemp = tf.WriteToTemp()
2409 stemp = sf.WriteToTemp()
2410
2411 ext = os.path.splitext(tf.name)[1]
2412
2413 try:
2414 ptemp = tempfile.NamedTemporaryFile()
2415 if isinstance(diff_program, list):
2416 cmd = copy.copy(diff_program)
2417 else:
2418 cmd = [diff_program]
2419 cmd.append(stemp.name)
2420 cmd.append(ttemp.name)
2421 cmd.append(ptemp.name)
2422 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002423 err = []
2424 def run():
2425 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002426 if e:
2427 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002428 th = threading.Thread(target=run)
2429 th.start()
2430 th.join(timeout=300) # 5 mins
2431 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002432 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002433 p.terminate()
2434 th.join(5)
2435 if th.is_alive():
2436 p.kill()
2437 th.join()
2438
Tianjie Xua2a9f992018-01-05 15:15:54 -08002439 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002440 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002441 self.patch = None
2442 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002443 diff = ptemp.read()
2444 finally:
2445 ptemp.close()
2446 stemp.close()
2447 ttemp.close()
2448
2449 self.patch = diff
2450 return self.tf, self.sf, self.patch
2451
2452
2453 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002454 """Returns a tuple of (target_file, source_file, patch_data).
2455
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002456 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002457 computing the patch failed.
2458 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002459 return self.tf, self.sf, self.patch
2460
2461
2462def ComputeDifferences(diffs):
2463 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002464 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002465
2466 # Do the largest files first, to try and reduce the long-pole effect.
2467 by_size = [(i.tf.size, i) for i in diffs]
2468 by_size.sort(reverse=True)
2469 by_size = [i[1] for i in by_size]
2470
2471 lock = threading.Lock()
2472 diff_iter = iter(by_size) # accessed under lock
2473
2474 def worker():
2475 try:
2476 lock.acquire()
2477 for d in diff_iter:
2478 lock.release()
2479 start = time.time()
2480 d.ComputePatch()
2481 dur = time.time() - start
2482 lock.acquire()
2483
2484 tf, sf, patch = d.GetPatch()
2485 if sf.name == tf.name:
2486 name = tf.name
2487 else:
2488 name = "%s (%s)" % (tf.name, sf.name)
2489 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002490 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002491 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002492 logger.info(
2493 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2494 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002495 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002496 except Exception:
2497 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002498 raise
2499
2500 # start worker threads; wait for them all to finish.
2501 threads = [threading.Thread(target=worker)
2502 for i in range(OPTIONS.worker_threads)]
2503 for th in threads:
2504 th.start()
2505 while threads:
2506 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002507
2508
Dan Albert8b72aef2015-03-23 19:13:21 -07002509class BlockDifference(object):
2510 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002511 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002512 self.tgt = tgt
2513 self.src = src
2514 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002515 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002516 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002517
Tao Baodd2a5892015-03-12 12:32:37 -07002518 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002519 version = max(
2520 int(i) for i in
2521 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002522 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002523 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002524
Tianjie Xu41976c72019-07-03 13:57:01 -07002525 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2526 version=self.version,
2527 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002528 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002529 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002530 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002531 self.touched_src_ranges = b.touched_src_ranges
2532 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002533
Yifan Hong10c530d2018-12-27 17:34:18 -08002534 # On devices with dynamic partitions, for new partitions,
2535 # src is None but OPTIONS.source_info_dict is not.
2536 if OPTIONS.source_info_dict is None:
2537 is_dynamic_build = OPTIONS.info_dict.get(
2538 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002539 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002540 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002541 is_dynamic_build = OPTIONS.source_info_dict.get(
2542 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002543 is_dynamic_source = partition in shlex.split(
2544 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002545
Yifan Hongbb2658d2019-01-25 12:30:58 -08002546 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002547 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2548
Yifan Hongbb2658d2019-01-25 12:30:58 -08002549 # For dynamic partitions builds, check partition list in both source
2550 # and target build because new partitions may be added, and existing
2551 # partitions may be removed.
2552 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2553
Yifan Hong10c530d2018-12-27 17:34:18 -08002554 if is_dynamic:
2555 self.device = 'map_partition("%s")' % partition
2556 else:
2557 if OPTIONS.source_info_dict is None:
2558 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2559 else:
2560 _, device_path = GetTypeAndDevice("/" + partition,
2561 OPTIONS.source_info_dict)
2562 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002563
Tao Baod8d14be2016-02-04 14:26:02 -08002564 @property
2565 def required_cache(self):
2566 return self._required_cache
2567
Tao Bao76def242017-11-21 09:25:31 -08002568 def WriteScript(self, script, output_zip, progress=None,
2569 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002570 if not self.src:
2571 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002572 script.Print("Patching %s image unconditionally..." % (self.partition,))
2573 else:
2574 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002575
Dan Albert8b72aef2015-03-23 19:13:21 -07002576 if progress:
2577 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002578 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002579
2580 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002581 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002582
Tao Bao9bc6bb22015-11-09 16:58:28 -08002583 def WriteStrictVerifyScript(self, script):
2584 """Verify all the blocks in the care_map, including clobbered blocks.
2585
2586 This differs from the WriteVerifyScript() function: a) it prints different
2587 error messages; b) it doesn't allow half-way updated images to pass the
2588 verification."""
2589
2590 partition = self.partition
2591 script.Print("Verifying %s..." % (partition,))
2592 ranges = self.tgt.care_map
2593 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002594 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002595 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2596 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002597 self.device, ranges_str,
2598 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002599 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002600 script.AppendExtra("")
2601
Tao Baod522bdc2016-04-12 15:53:16 -07002602 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002603 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002604
2605 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002606 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002607 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002608
2609 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002610 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002611 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002612 ranges = self.touched_src_ranges
2613 expected_sha1 = self.touched_src_sha1
2614 else:
2615 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2616 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002617
2618 # No blocks to be checked, skipping.
2619 if not ranges:
2620 return
2621
Tao Bao5ece99d2015-05-12 11:42:31 -07002622 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002623 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002624 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002625 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2626 '"%s.patch.dat")) then' % (
2627 self.device, ranges_str, expected_sha1,
2628 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002629 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002630 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002631
Tianjie Xufc3422a2015-12-15 11:53:59 -08002632 if self.version >= 4:
2633
2634 # Bug: 21124327
2635 # When generating incrementals for the system and vendor partitions in
2636 # version 4 or newer, explicitly check the first block (which contains
2637 # the superblock) of the partition to see if it's what we expect. If
2638 # this check fails, give an explicit log message about the partition
2639 # having been remounted R/W (the most likely explanation).
2640 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002641 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002642
2643 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002644 if partition == "system":
2645 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2646 else:
2647 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002648 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002649 'ifelse (block_image_recover({device}, "{ranges}") && '
2650 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002651 'package_extract_file("{partition}.transfer.list"), '
2652 '"{partition}.new.dat", "{partition}.patch.dat"), '
2653 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002654 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002655 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002656 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002657
Tao Baodd2a5892015-03-12 12:32:37 -07002658 # Abort the OTA update. Note that the incremental OTA cannot be applied
2659 # even if it may match the checksum of the target partition.
2660 # a) If version < 3, operations like move and erase will make changes
2661 # unconditionally and damage the partition.
2662 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002663 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002664 if partition == "system":
2665 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2666 else:
2667 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2668 script.AppendExtra((
2669 'abort("E%d: %s partition has unexpected contents");\n'
2670 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002671
Yifan Hong10c530d2018-12-27 17:34:18 -08002672 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002673 partition = self.partition
2674 script.Print('Verifying the updated %s image...' % (partition,))
2675 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2676 ranges = self.tgt.care_map
2677 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002678 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002679 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002680 self.device, ranges_str,
2681 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002682
2683 # Bug: 20881595
2684 # Verify that extended blocks are really zeroed out.
2685 if self.tgt.extended:
2686 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002687 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002688 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002689 self.device, ranges_str,
2690 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002691 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002692 if partition == "system":
2693 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2694 else:
2695 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002696 script.AppendExtra(
2697 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002698 ' abort("E%d: %s partition has unexpected non-zero contents after '
2699 'OTA update");\n'
2700 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002701 else:
2702 script.Print('Verified the updated %s image.' % (partition,))
2703
Tianjie Xu209db462016-05-24 17:34:52 -07002704 if partition == "system":
2705 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2706 else:
2707 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2708
Tao Bao5fcaaef2015-06-01 13:40:49 -07002709 script.AppendExtra(
2710 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002711 ' abort("E%d: %s partition has unexpected contents after OTA '
2712 'update");\n'
2713 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002714
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002715 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002716 ZipWrite(output_zip,
2717 '{}.transfer.list'.format(self.path),
2718 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002719
Tao Bao76def242017-11-21 09:25:31 -08002720 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2721 # its size. Quailty 9 almost triples the compression time but doesn't
2722 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002723 # zip | brotli(quality 6) | brotli(quality 9)
2724 # compressed_size: 942M | 869M (~8% reduced) | 854M
2725 # compression_time: 75s | 265s | 719s
2726 # decompression_time: 15s | 25s | 25s
2727
2728 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002729 brotli_cmd = ['brotli', '--quality=6',
2730 '--output={}.new.dat.br'.format(self.path),
2731 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002732 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002733 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002734
2735 new_data_name = '{}.new.dat.br'.format(self.partition)
2736 ZipWrite(output_zip,
2737 '{}.new.dat.br'.format(self.path),
2738 new_data_name,
2739 compress_type=zipfile.ZIP_STORED)
2740 else:
2741 new_data_name = '{}.new.dat'.format(self.partition)
2742 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2743
Dan Albert8e0178d2015-01-27 15:53:15 -08002744 ZipWrite(output_zip,
2745 '{}.patch.dat'.format(self.path),
2746 '{}.patch.dat'.format(self.partition),
2747 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002748
Tianjie Xu209db462016-05-24 17:34:52 -07002749 if self.partition == "system":
2750 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2751 else:
2752 code = ErrorCode.VENDOR_UPDATE_FAILURE
2753
Yifan Hong10c530d2018-12-27 17:34:18 -08002754 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002755 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002756 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002757 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002758 device=self.device, partition=self.partition,
2759 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002760 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002761
Dan Albert8b72aef2015-03-23 19:13:21 -07002762 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002763 data = source.ReadRangeSet(ranges)
2764 ctx = sha1()
2765
2766 for p in data:
2767 ctx.update(p)
2768
2769 return ctx.hexdigest()
2770
Tao Baoe9b61912015-07-09 17:37:49 -07002771 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2772 """Return the hash value for all zero blocks."""
2773 zero_block = '\x00' * 4096
2774 ctx = sha1()
2775 for _ in range(num_blocks):
2776 ctx.update(zero_block)
2777
2778 return ctx.hexdigest()
2779
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002780
Tianjie Xu41976c72019-07-03 13:57:01 -07002781# Expose these two classes to support vendor-specific scripts
2782DataImage = images.DataImage
2783EmptyImage = images.EmptyImage
2784
Tao Bao76def242017-11-21 09:25:31 -08002785
Doug Zongker96a57e72010-09-26 14:57:41 -07002786# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002787PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002788 "ext4": "EMMC",
2789 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002790 "f2fs": "EMMC",
2791 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002792}
Doug Zongker96a57e72010-09-26 14:57:41 -07002793
Tao Bao76def242017-11-21 09:25:31 -08002794
Doug Zongker96a57e72010-09-26 14:57:41 -07002795def GetTypeAndDevice(mount_point, info):
2796 fstab = info["fstab"]
2797 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002798 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2799 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002800 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002801 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002802
2803
2804def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002805 """Parses and converts a PEM-encoded certificate into DER-encoded.
2806
2807 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2808
2809 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002810 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002811 """
2812 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002813 save = False
2814 for line in data.split("\n"):
2815 if "--END CERTIFICATE--" in line:
2816 break
2817 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002818 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002819 if "--BEGIN CERTIFICATE--" in line:
2820 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002821 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002822 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002823
Tao Bao04e1f012018-02-04 12:13:35 -08002824
2825def ExtractPublicKey(cert):
2826 """Extracts the public key (PEM-encoded) from the given certificate file.
2827
2828 Args:
2829 cert: The certificate filename.
2830
2831 Returns:
2832 The public key string.
2833
2834 Raises:
2835 AssertionError: On non-zero return from 'openssl'.
2836 """
2837 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2838 # While openssl 1.1 writes the key into the given filename followed by '-out',
2839 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2840 # stdout instead.
2841 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2842 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2843 pubkey, stderrdata = proc.communicate()
2844 assert proc.returncode == 0, \
2845 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2846 return pubkey
2847
2848
Tao Bao1ac886e2019-06-26 11:58:22 -07002849def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002850 """Extracts the AVB public key from the given public or private key.
2851
2852 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002853 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002854 key: The input key file, which should be PEM-encoded public or private key.
2855
2856 Returns:
2857 The path to the extracted AVB public key file.
2858 """
2859 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2860 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002861 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002862 return output
2863
2864
Doug Zongker412c02f2014-02-13 10:58:24 -08002865def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2866 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002867 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002868
Tao Bao6d5d6232018-03-09 17:04:42 -08002869 Most of the space in the boot and recovery images is just the kernel, which is
2870 identical for the two, so the resulting patch should be efficient. Add it to
2871 the output zip, along with a shell script that is run from init.rc on first
2872 boot to actually do the patching and install the new recovery image.
2873
2874 Args:
2875 input_dir: The top-level input directory of the target-files.zip.
2876 output_sink: The callback function that writes the result.
2877 recovery_img: File object for the recovery image.
2878 boot_img: File objects for the boot image.
2879 info_dict: A dict returned by common.LoadInfoDict() on the input
2880 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002881 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002882 if info_dict is None:
2883 info_dict = OPTIONS.info_dict
2884
Tao Bao6d5d6232018-03-09 17:04:42 -08002885 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002886 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2887
2888 if board_uses_vendorimage:
2889 # In this case, the output sink is rooted at VENDOR
2890 recovery_img_path = "etc/recovery.img"
2891 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2892 sh_dir = "bin"
2893 else:
2894 # In this case the output sink is rooted at SYSTEM
2895 recovery_img_path = "vendor/etc/recovery.img"
2896 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2897 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002898
Tao Baof2cffbd2015-07-22 12:33:18 -07002899 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002900 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002901
2902 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002903 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002904 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002905 # With system-root-image, boot and recovery images will have mismatching
2906 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2907 # to handle such a case.
2908 if system_root_image:
2909 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002910 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002911 assert not os.path.exists(path)
2912 else:
2913 diff_program = ["imgdiff"]
2914 if os.path.exists(path):
2915 diff_program.append("-b")
2916 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002917 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002918 else:
2919 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002920
2921 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2922 _, _, patch = d.ComputePatch()
2923 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002924
Dan Albertebb19aa2015-03-27 19:11:53 -07002925 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002926 # The following GetTypeAndDevice()s need to use the path in the target
2927 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002928 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2929 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2930 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002931 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002932
Tao Baof2cffbd2015-07-22 12:33:18 -07002933 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002934
2935 # Note that we use /vendor to refer to the recovery resources. This will
2936 # work for a separate vendor partition mounted at /vendor or a
2937 # /system/vendor subdirectory on the system partition, for which init will
2938 # create a symlink from /vendor to /system/vendor.
2939
2940 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002941if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2942 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002943 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002944 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2945 log -t recovery "Installing new recovery image: succeeded" || \\
2946 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002947else
2948 log -t recovery "Recovery image already installed"
2949fi
2950""" % {'type': recovery_type,
2951 'device': recovery_device,
2952 'sha1': recovery_img.sha1,
2953 'size': recovery_img.size}
2954 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002955 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002956if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2957 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002958 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002959 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2960 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2961 log -t recovery "Installing new recovery image: succeeded" || \\
2962 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002963else
2964 log -t recovery "Recovery image already installed"
2965fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002966""" % {'boot_size': boot_img.size,
2967 'boot_sha1': boot_img.sha1,
2968 'recovery_size': recovery_img.size,
2969 'recovery_sha1': recovery_img.sha1,
2970 'boot_type': boot_type,
2971 'boot_device': boot_device,
2972 'recovery_type': recovery_type,
2973 'recovery_device': recovery_device,
2974 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002975
Bill Peckhame868aec2019-09-17 17:06:47 -07002976 # The install script location moved from /system/etc to /system/bin in the L
2977 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2978 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002979
Tao Bao32fcdab2018-10-12 10:30:39 -07002980 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002981
Tao Baoda30cfa2017-12-01 16:19:46 -08002982 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002983
2984
2985class DynamicPartitionUpdate(object):
2986 def __init__(self, src_group=None, tgt_group=None, progress=None,
2987 block_difference=None):
2988 self.src_group = src_group
2989 self.tgt_group = tgt_group
2990 self.progress = progress
2991 self.block_difference = block_difference
2992
2993 @property
2994 def src_size(self):
2995 if not self.block_difference:
2996 return 0
2997 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2998
2999 @property
3000 def tgt_size(self):
3001 if not self.block_difference:
3002 return 0
3003 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3004
3005 @staticmethod
3006 def _GetSparseImageSize(img):
3007 if not img:
3008 return 0
3009 return img.blocksize * img.total_blocks
3010
3011
3012class DynamicGroupUpdate(object):
3013 def __init__(self, src_size=None, tgt_size=None):
3014 # None: group does not exist. 0: no size limits.
3015 self.src_size = src_size
3016 self.tgt_size = tgt_size
3017
3018
3019class DynamicPartitionsDifference(object):
3020 def __init__(self, info_dict, block_diffs, progress_dict=None,
3021 source_info_dict=None):
3022 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003023 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003024
3025 self._remove_all_before_apply = False
3026 if source_info_dict is None:
3027 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003028 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003029
Tao Baof1113e92019-06-18 12:10:14 -07003030 block_diff_dict = collections.OrderedDict(
3031 [(e.partition, e) for e in block_diffs])
3032
Yifan Hong10c530d2018-12-27 17:34:18 -08003033 assert len(block_diff_dict) == len(block_diffs), \
3034 "Duplicated BlockDifference object for {}".format(
3035 [partition for partition, count in
3036 collections.Counter(e.partition for e in block_diffs).items()
3037 if count > 1])
3038
Yifan Hong79997e52019-01-23 16:56:19 -08003039 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003040
3041 for p, block_diff in block_diff_dict.items():
3042 self._partition_updates[p] = DynamicPartitionUpdate()
3043 self._partition_updates[p].block_difference = block_diff
3044
3045 for p, progress in progress_dict.items():
3046 if p in self._partition_updates:
3047 self._partition_updates[p].progress = progress
3048
3049 tgt_groups = shlex.split(info_dict.get(
3050 "super_partition_groups", "").strip())
3051 src_groups = shlex.split(source_info_dict.get(
3052 "super_partition_groups", "").strip())
3053
3054 for g in tgt_groups:
3055 for p in shlex.split(info_dict.get(
3056 "super_%s_partition_list" % g, "").strip()):
3057 assert p in self._partition_updates, \
3058 "{} is in target super_{}_partition_list but no BlockDifference " \
3059 "object is provided.".format(p, g)
3060 self._partition_updates[p].tgt_group = g
3061
3062 for g in src_groups:
3063 for p in shlex.split(source_info_dict.get(
3064 "super_%s_partition_list" % g, "").strip()):
3065 assert p in self._partition_updates, \
3066 "{} is in source super_{}_partition_list but no BlockDifference " \
3067 "object is provided.".format(p, g)
3068 self._partition_updates[p].src_group = g
3069
Yifan Hong45433e42019-01-18 13:55:25 -08003070 target_dynamic_partitions = set(shlex.split(info_dict.get(
3071 "dynamic_partition_list", "").strip()))
3072 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3073 if u.tgt_size)
3074 assert block_diffs_with_target == target_dynamic_partitions, \
3075 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3076 list(target_dynamic_partitions), list(block_diffs_with_target))
3077
3078 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3079 "dynamic_partition_list", "").strip()))
3080 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3081 if u.src_size)
3082 assert block_diffs_with_source == source_dynamic_partitions, \
3083 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3084 list(source_dynamic_partitions), list(block_diffs_with_source))
3085
Yifan Hong10c530d2018-12-27 17:34:18 -08003086 if self._partition_updates:
3087 logger.info("Updating dynamic partitions %s",
3088 self._partition_updates.keys())
3089
Yifan Hong79997e52019-01-23 16:56:19 -08003090 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003091
3092 for g in tgt_groups:
3093 self._group_updates[g] = DynamicGroupUpdate()
3094 self._group_updates[g].tgt_size = int(info_dict.get(
3095 "super_%s_group_size" % g, "0").strip())
3096
3097 for g in src_groups:
3098 if g not in self._group_updates:
3099 self._group_updates[g] = DynamicGroupUpdate()
3100 self._group_updates[g].src_size = int(source_info_dict.get(
3101 "super_%s_group_size" % g, "0").strip())
3102
3103 self._Compute()
3104
3105 def WriteScript(self, script, output_zip, write_verify_script=False):
3106 script.Comment('--- Start patching dynamic partitions ---')
3107 for p, u in self._partition_updates.items():
3108 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3109 script.Comment('Patch partition %s' % p)
3110 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3111 write_verify_script=False)
3112
3113 op_list_path = MakeTempFile()
3114 with open(op_list_path, 'w') as f:
3115 for line in self._op_list:
3116 f.write('{}\n'.format(line))
3117
3118 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3119
3120 script.Comment('Update dynamic partition metadata')
3121 script.AppendExtra('assert(update_dynamic_partitions('
3122 'package_extract_file("dynamic_partitions_op_list")));')
3123
3124 if write_verify_script:
3125 for p, u in self._partition_updates.items():
3126 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3127 u.block_difference.WritePostInstallVerifyScript(script)
3128 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3129
3130 for p, u in self._partition_updates.items():
3131 if u.tgt_size and u.src_size <= u.tgt_size:
3132 script.Comment('Patch partition %s' % p)
3133 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3134 write_verify_script=write_verify_script)
3135 if write_verify_script:
3136 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3137
3138 script.Comment('--- End patching dynamic partitions ---')
3139
3140 def _Compute(self):
3141 self._op_list = list()
3142
3143 def append(line):
3144 self._op_list.append(line)
3145
3146 def comment(line):
3147 self._op_list.append("# %s" % line)
3148
3149 if self._remove_all_before_apply:
3150 comment('Remove all existing dynamic partitions and groups before '
3151 'applying full OTA')
3152 append('remove_all_groups')
3153
3154 for p, u in self._partition_updates.items():
3155 if u.src_group and not u.tgt_group:
3156 append('remove %s' % p)
3157
3158 for p, u in self._partition_updates.items():
3159 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3160 comment('Move partition %s from %s to default' % (p, u.src_group))
3161 append('move %s default' % p)
3162
3163 for p, u in self._partition_updates.items():
3164 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3165 comment('Shrink partition %s from %d to %d' %
3166 (p, u.src_size, u.tgt_size))
3167 append('resize %s %s' % (p, u.tgt_size))
3168
3169 for g, u in self._group_updates.items():
3170 if u.src_size is not None and u.tgt_size is None:
3171 append('remove_group %s' % g)
3172 if (u.src_size is not None and u.tgt_size is not None and
3173 u.src_size > u.tgt_size):
3174 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3175 append('resize_group %s %d' % (g, u.tgt_size))
3176
3177 for g, u in self._group_updates.items():
3178 if u.src_size is None and u.tgt_size is not None:
3179 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3180 append('add_group %s %d' % (g, u.tgt_size))
3181 if (u.src_size is not None and u.tgt_size is not None and
3182 u.src_size < u.tgt_size):
3183 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3184 append('resize_group %s %d' % (g, u.tgt_size))
3185
3186 for p, u in self._partition_updates.items():
3187 if u.tgt_group and not u.src_group:
3188 comment('Add partition %s to group %s' % (p, u.tgt_group))
3189 append('add %s %s' % (p, u.tgt_group))
3190
3191 for p, u in self._partition_updates.items():
3192 if u.tgt_size and u.src_size < u.tgt_size:
3193 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3194 append('resize %s %d' % (p, u.tgt_size))
3195
3196 for p, u in self._partition_updates.items():
3197 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3198 comment('Move partition %s from default to %s' %
3199 (p, u.tgt_group))
3200 append('move %s %s' % (p, u.tgt_group))