blob: 7805e30ce84f8905ce1ea8f6d356027dc3d770fc [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070020import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070021import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070022import getopt
23import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010024import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070025import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070026import json
27import logging
28import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070029import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080030import platform
Doug Zongkereef39442009-04-02 12:14:19 -070031import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070032import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070033import shutil
34import subprocess
35import sys
36import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070037import threading
38import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070039import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080040from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070041
Tianjie Xu41976c72019-07-03 13:57:01 -070042import images
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070044from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070045
Tao Bao32fcdab2018-10-12 10:30:39 -070046logger = logging.getLogger(__name__)
47
Tao Bao986ee862018-10-04 15:46:16 -070048
Dan Albert8b72aef2015-03-23 19:13:21 -070049class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070052 # Set up search path, in order to find framework/ and lib64/. At the time of
53 # running this function, user-supplied search path (`--path`) hasn't been
54 # available. So the value set here is the default, which might be overridden
55 # by commandline flag later.
56 exec_path = sys.argv[0]
57 if exec_path.endswith('.py'):
58 script_name = os.path.basename(exec_path)
59 # logger hasn't been initialized yet at this point. Use print to output
60 # warnings.
61 print(
62 'Warning: releasetools script should be invoked as hermetic Python '
63 'executable -- build and run `{}` directly.'.format(script_name[:-3]),
64 file=sys.stderr)
Robin Lee34ea7392020-01-02 20:21:18 +010065 self.search_path = os.path.realpath(os.path.join(os.path.dirname(exec_path), '..'))
Pavel Salomatov32676552019-03-06 20:00:45 +030066
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080068 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.extra_signapk_args = []
70 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080071 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080072 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070073 self.public_key_suffix = ".x509.pem"
74 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070075 # use otatools built boot_signer by default
76 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070077 self.boot_signer_args = []
78 self.verity_signer_path = None
79 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070080 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080081 self.aftl_server = None
82 self.aftl_key_path = None
83 self.aftl_manufacturer_key_path = None
84 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070085 self.verbose = False
86 self.tempfiles = []
87 self.device_specific = None
88 self.extras = {}
89 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070090 self.source_info_dict = None
91 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070092 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070093 # Stash size cannot exceed cache_size * threshold.
94 self.cache_size = None
95 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070096 self.logfile = None
Dan Albert8b72aef2015-03-23 19:13:21 -070097
98
99OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700100
Tao Bao71197512018-10-11 14:08:45 -0700101# The block size that's used across the releasetools scripts.
102BLOCK_SIZE = 4096
103
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800104# Values for "certificate" in apkcerts that mean special things.
105SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
106
Tao Bao5cc0abb2019-03-21 10:18:05 -0700107# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
108# that system_other is not in the list because we don't want to include its
109# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900110AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Steve Mucklee1b10862019-07-10 10:49:37 -0700111 'system_ext', 'vendor', 'vendor_boot')
Tao Bao9dd909e2017-11-14 11:27:32 -0800112
Tao Bao08c190f2019-06-03 23:07:58 -0700113# Chained VBMeta partitions.
114AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
115
Tianjie Xu861f4132018-09-12 11:49:33 -0700116# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900117PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700118
119
Tianjie Xu209db462016-05-24 17:34:52 -0700120class ErrorCode(object):
121 """Define error_codes for failures that happen during the actual
122 update package installation.
123
124 Error codes 0-999 are reserved for failures before the package
125 installation (i.e. low battery, package verification failure).
126 Detailed code in 'bootable/recovery/error_code.h' """
127
128 SYSTEM_VERIFICATION_FAILURE = 1000
129 SYSTEM_UPDATE_FAILURE = 1001
130 SYSTEM_UNEXPECTED_CONTENTS = 1002
131 SYSTEM_NONZERO_CONTENTS = 1003
132 SYSTEM_RECOVER_FAILURE = 1004
133 VENDOR_VERIFICATION_FAILURE = 2000
134 VENDOR_UPDATE_FAILURE = 2001
135 VENDOR_UNEXPECTED_CONTENTS = 2002
136 VENDOR_NONZERO_CONTENTS = 2003
137 VENDOR_RECOVER_FAILURE = 2004
138 OEM_PROP_MISMATCH = 3000
139 FINGERPRINT_MISMATCH = 3001
140 THUMBPRINT_MISMATCH = 3002
141 OLDER_BUILD = 3003
142 DEVICE_MISMATCH = 3004
143 BAD_PATCH_FILE = 3005
144 INSUFFICIENT_CACHE_SPACE = 3006
145 TUNE_PARTITION_FAILURE = 3007
146 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800147
Tao Bao80921982018-03-21 21:02:19 -0700148
Dan Albert8b72aef2015-03-23 19:13:21 -0700149class ExternalError(RuntimeError):
150 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700151
152
Tao Bao32fcdab2018-10-12 10:30:39 -0700153def InitLogging():
154 DEFAULT_LOGGING_CONFIG = {
155 'version': 1,
156 'disable_existing_loggers': False,
157 'formatters': {
158 'standard': {
159 'format':
160 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
161 'datefmt': '%Y-%m-%d %H:%M:%S',
162 },
163 },
164 'handlers': {
165 'default': {
166 'class': 'logging.StreamHandler',
167 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700168 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700169 },
170 },
171 'loggers': {
172 '': {
173 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700174 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700175 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700176 }
177 }
178 }
179 env_config = os.getenv('LOGGING_CONFIG')
180 if env_config:
181 with open(env_config) as f:
182 config = json.load(f)
183 else:
184 config = DEFAULT_LOGGING_CONFIG
185
186 # Increase the logging level for verbose mode.
187 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700188 config = copy.deepcopy(config)
189 config['handlers']['default']['level'] = 'INFO'
190
191 if OPTIONS.logfile:
192 config = copy.deepcopy(config)
193 config['handlers']['logfile'] = {
194 'class': 'logging.FileHandler',
195 'formatter': 'standard',
196 'level': 'INFO',
197 'mode': 'w',
198 'filename': OPTIONS.logfile,
199 }
200 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700201
202 logging.config.dictConfig(config)
203
204
Tao Bao39451582017-05-04 11:10:47 -0700205def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700206 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700207
Tao Bao73dd4f42018-10-04 16:25:33 -0700208 Args:
209 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700210 verbose: Whether the commands should be shown. Default to the global
211 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700212 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
213 stdin, etc. stdout and stderr will default to subprocess.PIPE and
214 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800215 universal_newlines will default to True, as most of the users in
216 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700217
218 Returns:
219 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700220 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700221 if 'stdout' not in kwargs and 'stderr' not in kwargs:
222 kwargs['stdout'] = subprocess.PIPE
223 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800224 if 'universal_newlines' not in kwargs:
225 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700226 # Don't log any if caller explicitly says so.
227 if verbose != False:
228 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700229 return subprocess.Popen(args, **kwargs)
230
231
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800232def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800233 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800234
235 Args:
236 args: The command represented as a list of strings.
237 verbose: Whether the commands should be shown. Default to the global
238 verbosity if unspecified.
239 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
240 stdin, etc. stdout and stderr will default to subprocess.PIPE and
241 subprocess.STDOUT respectively unless caller specifies any of them.
242
Bill Peckham889b0c62019-02-21 18:53:37 -0800243 Raises:
244 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800245 """
246 proc = Run(args, verbose=verbose, **kwargs)
247 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800248
249 if proc.returncode != 0:
250 raise ExternalError(
251 "Failed to run command '{}' (exit code {})".format(
252 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800253
254
Tao Bao986ee862018-10-04 15:46:16 -0700255def RunAndCheckOutput(args, verbose=None, **kwargs):
256 """Runs the given command and returns the output.
257
258 Args:
259 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700260 verbose: Whether the commands should be shown. Default to the global
261 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700262 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
263 stdin, etc. stdout and stderr will default to subprocess.PIPE and
264 subprocess.STDOUT respectively unless caller specifies any of them.
265
266 Returns:
267 The output string.
268
269 Raises:
270 ExternalError: On non-zero exit from the command.
271 """
Tao Bao986ee862018-10-04 15:46:16 -0700272 proc = Run(args, verbose=verbose, **kwargs)
273 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800274 if output is None:
275 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700276 # Don't log any if caller explicitly says so.
277 if verbose != False:
278 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700279 if proc.returncode != 0:
280 raise ExternalError(
281 "Failed to run command '{}' (exit code {}):\n{}".format(
282 args, proc.returncode, output))
283 return output
284
285
Tao Baoc765cca2018-01-31 17:32:40 -0800286def RoundUpTo4K(value):
287 rounded_up = value + 4095
288 return rounded_up - (rounded_up % 4096)
289
290
Ying Wang7e6d4e42010-12-13 16:25:36 -0800291def CloseInheritedPipes():
292 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
293 before doing other work."""
294 if platform.system() != "Darwin":
295 return
296 for d in range(3, 1025):
297 try:
298 stat = os.fstat(d)
299 if stat is not None:
300 pipebit = stat[0] & 0x1000
301 if pipebit != 0:
302 os.close(d)
303 except OSError:
304 pass
305
306
Tao Bao1c320f82019-10-04 23:25:12 -0700307class BuildInfo(object):
308 """A class that holds the information for a given build.
309
310 This class wraps up the property querying for a given source or target build.
311 It abstracts away the logic of handling OEM-specific properties, and caches
312 the commonly used properties such as fingerprint.
313
314 There are two types of info dicts: a) build-time info dict, which is generated
315 at build time (i.e. included in a target_files zip); b) OEM info dict that is
316 specified at package generation time (via command line argument
317 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
318 having "oem_fingerprint_properties" in build-time info dict), all the queries
319 would be answered based on build-time info dict only. Otherwise if using
320 OEM-specific properties, some of them will be calculated from two info dicts.
321
322 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800323 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700324
325 Attributes:
326 info_dict: The build-time info dict.
327 is_ab: Whether it's a build that uses A/B OTA.
328 oem_dicts: A list of OEM dicts.
329 oem_props: A list of OEM properties that should be read from OEM dicts; None
330 if the build doesn't use any OEM-specific property.
331 fingerprint: The fingerprint of the build, which would be calculated based
332 on OEM properties if applicable.
333 device: The device name, which could come from OEM dicts if applicable.
334 """
335
336 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
337 "ro.product.manufacturer", "ro.product.model",
338 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700339 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
340 "product", "odm", "vendor", "system_ext", "system"]
341 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
342 "product", "product_services", "odm", "vendor", "system"]
343 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700344
Tao Bao3ed35d32019-10-07 20:48:48 -0700345 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700346 """Initializes a BuildInfo instance with the given dicts.
347
348 Note that it only wraps up the given dicts, without making copies.
349
350 Arguments:
351 info_dict: The build-time info dict.
352 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
353 that it always uses the first dict to calculate the fingerprint or the
354 device name. The rest would be used for asserting OEM properties only
355 (e.g. one package can be installed on one of these devices).
356
357 Raises:
358 ValueError: On invalid inputs.
359 """
360 self.info_dict = info_dict
361 self.oem_dicts = oem_dicts
362
363 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700364
Hongguang Chend7c160f2020-05-03 21:24:26 -0700365 # Skip _oem_props if oem_dicts is None to use BuildInfo in
366 # sign_target_files_apks
367 if self.oem_dicts:
368 self._oem_props = info_dict.get("oem_fingerprint_properties")
369 else:
370 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700371
Daniel Normand5fe8622020-01-08 17:01:11 -0800372 def check_fingerprint(fingerprint):
373 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
374 raise ValueError(
375 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
376 "3.2.2. Build Parameters.".format(fingerprint))
377
378
379 self._partition_fingerprints = {}
380 for partition in PARTITIONS_WITH_CARE_MAP:
381 try:
382 fingerprint = self.CalculatePartitionFingerprint(partition)
383 check_fingerprint(fingerprint)
384 self._partition_fingerprints[partition] = fingerprint
385 except ExternalError:
386 continue
387 if "system" in self._partition_fingerprints:
388 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
389 # need a fingerprint when creating the image.
390 self._partition_fingerprints[
391 "system_other"] = self._partition_fingerprints["system"]
392
Tao Bao1c320f82019-10-04 23:25:12 -0700393 # These two should be computed only after setting self._oem_props.
394 self._device = self.GetOemProperty("ro.product.device")
395 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800396 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700397
398 @property
399 def is_ab(self):
400 return self._is_ab
401
402 @property
403 def device(self):
404 return self._device
405
406 @property
407 def fingerprint(self):
408 return self._fingerprint
409
410 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700411 def oem_props(self):
412 return self._oem_props
413
414 def __getitem__(self, key):
415 return self.info_dict[key]
416
417 def __setitem__(self, key, value):
418 self.info_dict[key] = value
419
420 def get(self, key, default=None):
421 return self.info_dict.get(key, default)
422
423 def items(self):
424 return self.info_dict.items()
425
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000426 def _GetRawBuildProp(self, prop, partition):
427 prop_file = '{}.build.prop'.format(
428 partition) if partition else 'build.prop'
429 partition_props = self.info_dict.get(prop_file)
430 if not partition_props:
431 return None
432 return partition_props.GetProp(prop)
433
Daniel Normand5fe8622020-01-08 17:01:11 -0800434 def GetPartitionBuildProp(self, prop, partition):
435 """Returns the inquired build property for the provided partition."""
436 # If provided a partition for this property, only look within that
437 # partition's build.prop.
438 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
439 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
440 else:
441 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000442
443 prop_val = self._GetRawBuildProp(prop, partition)
444 if prop_val is not None:
445 return prop_val
446 raise ExternalError("couldn't find %s in %s.build.prop" %
447 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800448
Tao Bao1c320f82019-10-04 23:25:12 -0700449 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800450 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700451 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
452 return self._ResolveRoProductBuildProp(prop)
453
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000454 prop_val = self._GetRawBuildProp(prop, None)
455 if prop_val is not None:
456 return prop_val
457
458 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700459
460 def _ResolveRoProductBuildProp(self, prop):
461 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000462 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700463 if prop_val:
464 return prop_val
465
Steven Laver8e2086e2020-04-27 16:26:31 -0700466 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000467 source_order_val = self._GetRawBuildProp(
468 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700469 if source_order_val:
470 source_order = source_order_val.split(",")
471 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700472 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700473
474 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700475 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700476 raise ExternalError(
477 "Invalid ro.product.property_source_order '{}'".format(source_order))
478
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000479 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700480 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000481 "ro.product", "ro.product.{}".format(source_partition), 1)
482 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700483 if prop_val:
484 return prop_val
485
486 raise ExternalError("couldn't resolve {}".format(prop))
487
Steven Laver8e2086e2020-04-27 16:26:31 -0700488 def _GetRoProductPropsDefaultSourceOrder(self):
489 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
490 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000491 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700492 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000493 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700494 if android_version == "10":
495 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
496 # NOTE: float() conversion of android_version will have rounding error.
497 # We are checking for "9" or less, and using "< 10" is well outside of
498 # possible floating point rounding.
499 try:
500 android_version_val = float(android_version)
501 except ValueError:
502 android_version_val = 0
503 if android_version_val < 10:
504 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
505 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
506
Tao Bao1c320f82019-10-04 23:25:12 -0700507 def GetOemProperty(self, key):
508 if self.oem_props is not None and key in self.oem_props:
509 return self.oem_dicts[0][key]
510 return self.GetBuildProp(key)
511
Daniel Normand5fe8622020-01-08 17:01:11 -0800512 def GetPartitionFingerprint(self, partition):
513 return self._partition_fingerprints.get(partition, None)
514
515 def CalculatePartitionFingerprint(self, partition):
516 try:
517 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
518 except ExternalError:
519 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
520 self.GetPartitionBuildProp("ro.product.brand", partition),
521 self.GetPartitionBuildProp("ro.product.name", partition),
522 self.GetPartitionBuildProp("ro.product.device", partition),
523 self.GetPartitionBuildProp("ro.build.version.release", partition),
524 self.GetPartitionBuildProp("ro.build.id", partition),
525 self.GetPartitionBuildProp("ro.build.version.incremental", partition),
526 self.GetPartitionBuildProp("ro.build.type", partition),
527 self.GetPartitionBuildProp("ro.build.tags", partition))
528
Tao Bao1c320f82019-10-04 23:25:12 -0700529 def CalculateFingerprint(self):
530 if self.oem_props is None:
531 try:
532 return self.GetBuildProp("ro.build.fingerprint")
533 except ExternalError:
534 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
535 self.GetBuildProp("ro.product.brand"),
536 self.GetBuildProp("ro.product.name"),
537 self.GetBuildProp("ro.product.device"),
538 self.GetBuildProp("ro.build.version.release"),
539 self.GetBuildProp("ro.build.id"),
540 self.GetBuildProp("ro.build.version.incremental"),
541 self.GetBuildProp("ro.build.type"),
542 self.GetBuildProp("ro.build.tags"))
543 return "%s/%s/%s:%s" % (
544 self.GetOemProperty("ro.product.brand"),
545 self.GetOemProperty("ro.product.name"),
546 self.GetOemProperty("ro.product.device"),
547 self.GetBuildProp("ro.build.thumbprint"))
548
549 def WriteMountOemScript(self, script):
550 assert self.oem_props is not None
551 recovery_mount_options = self.info_dict.get("recovery_mount_options")
552 script.Mount("/oem", recovery_mount_options)
553
554 def WriteDeviceAssertions(self, script, oem_no_mount):
555 # Read the property directly if not using OEM properties.
556 if not self.oem_props:
557 script.AssertDevice(self.device)
558 return
559
560 # Otherwise assert OEM properties.
561 if not self.oem_dicts:
562 raise ExternalError(
563 "No OEM file provided to answer expected assertions")
564
565 for prop in self.oem_props.split():
566 values = []
567 for oem_dict in self.oem_dicts:
568 if prop in oem_dict:
569 values.append(oem_dict[prop])
570 if not values:
571 raise ExternalError(
572 "The OEM file is missing the property %s" % (prop,))
573 script.AssertOemProperty(prop, values, oem_no_mount)
574
575
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000576def ReadFromInputFile(input_file, fn):
577 """Reads the contents of fn from input zipfile or directory."""
578 if isinstance(input_file, zipfile.ZipFile):
579 return input_file.read(fn).decode()
580 else:
581 path = os.path.join(input_file, *fn.split("/"))
582 try:
583 with open(path) as f:
584 return f.read()
585 except IOError as e:
586 if e.errno == errno.ENOENT:
587 raise KeyError(fn)
588
589
Tao Bao410ad8b2018-08-24 12:08:38 -0700590def LoadInfoDict(input_file, repacking=False):
591 """Loads the key/value pairs from the given input target_files.
592
593 It reads `META/misc_info.txt` file in the target_files input, does sanity
594 checks and returns the parsed key/value pairs for to the given build. It's
595 usually called early when working on input target_files files, e.g. when
596 generating OTAs, or signing builds. Note that the function may be called
597 against an old target_files file (i.e. from past dessert releases). So the
598 property parsing needs to be backward compatible.
599
600 In a `META/misc_info.txt`, a few properties are stored as links to the files
601 in the PRODUCT_OUT directory. It works fine with the build system. However,
602 they are no longer available when (re)generating images from target_files zip.
603 When `repacking` is True, redirect these properties to the actual files in the
604 unzipped directory.
605
606 Args:
607 input_file: The input target_files file, which could be an open
608 zipfile.ZipFile instance, or a str for the dir that contains the files
609 unzipped from a target_files file.
610 repacking: Whether it's trying repack an target_files file after loading the
611 info dict (default: False). If so, it will rewrite a few loaded
612 properties (e.g. selinux_fc, root_dir) to point to the actual files in
613 target_files file. When doing repacking, `input_file` must be a dir.
614
615 Returns:
616 A dict that contains the parsed key/value pairs.
617
618 Raises:
619 AssertionError: On invalid input arguments.
620 ValueError: On malformed input values.
621 """
622 if repacking:
623 assert isinstance(input_file, str), \
624 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700625
Doug Zongkerc9253822014-02-04 12:17:58 -0800626 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000627 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800628
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700629 try:
Michael Runge6e836112014-04-15 17:40:21 -0700630 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700631 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700632 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700633
Tao Bao410ad8b2018-08-24 12:08:38 -0700634 if "recovery_api_version" not in d:
635 raise ValueError("Failed to find 'recovery_api_version'")
636 if "fstab_version" not in d:
637 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800638
Tao Bao410ad8b2018-08-24 12:08:38 -0700639 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700640 # "selinux_fc" properties should point to the file_contexts files
641 # (file_contexts.bin) under META/.
642 for key in d:
643 if key.endswith("selinux_fc"):
644 fc_basename = os.path.basename(d[key])
645 fc_config = os.path.join(input_file, "META", fc_basename)
646 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700647
Daniel Norman72c626f2019-05-13 15:58:14 -0700648 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700649
Tom Cherryd14b8952018-08-09 14:26:00 -0700650 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700651 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700652 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700653 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700654
David Anderson0ec64ac2019-12-06 12:21:18 -0800655 # Redirect {partition}_base_fs_file for each of the named partitions.
656 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
657 key_name = part_name + "_base_fs_file"
658 if key_name not in d:
659 continue
660 basename = os.path.basename(d[key_name])
661 base_fs_file = os.path.join(input_file, "META", basename)
662 if os.path.exists(base_fs_file):
663 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700664 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700665 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800666 "Failed to find %s base fs file: %s", part_name, base_fs_file)
667 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700668
Doug Zongker37974732010-09-16 17:44:38 -0700669 def makeint(key):
670 if key in d:
671 d[key] = int(d[key], 0)
672
673 makeint("recovery_api_version")
674 makeint("blocksize")
675 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700676 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700677 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700678 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700679 makeint("recovery_size")
680 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800681 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700682
Tao Bao765668f2019-10-04 22:03:00 -0700683 # Load recovery fstab if applicable.
684 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800685
Tianjie Xu861f4132018-09-12 11:49:33 -0700686 # Tries to load the build props for all partitions with care_map, including
687 # system and vendor.
688 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800689 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000690 d[partition_prop] = PartitionBuildProps.FromInputFile(
691 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700692 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800693
Tao Bao3ed35d32019-10-07 20:48:48 -0700694 # Set up the salt (based on fingerprint) that will be used when adding AVB
695 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800696 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700697 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800698 for partition in PARTITIONS_WITH_CARE_MAP:
699 fingerprint = build_info.GetPartitionFingerprint(partition)
700 if fingerprint:
701 d["avb_{}_salt".format(partition)] = sha256(fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800702
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700703 return d
704
Tao Baod1de6f32017-03-01 16:38:48 -0800705
Daniel Norman4cc9df62019-07-18 10:11:07 -0700706def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900707 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700708 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900709
Daniel Norman4cc9df62019-07-18 10:11:07 -0700710
711def LoadDictionaryFromFile(file_path):
712 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900713 return LoadDictionaryFromLines(lines)
714
715
Michael Runge6e836112014-04-15 17:40:21 -0700716def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700717 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700718 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700719 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700720 if not line or line.startswith("#"):
721 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700722 if "=" in line:
723 name, value = line.split("=", 1)
724 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700725 return d
726
Tao Baod1de6f32017-03-01 16:38:48 -0800727
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000728class PartitionBuildProps(object):
729 """The class holds the build prop of a particular partition.
730
731 This class loads the build.prop and holds the build properties for a given
732 partition. It also partially recognizes the 'import' statement in the
733 build.prop; and calculates alternative values of some specific build
734 properties during runtime.
735
736 Attributes:
737 input_file: a zipped target-file or an unzipped target-file directory.
738 partition: name of the partition.
739 props_allow_override: a list of build properties to search for the
740 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000741 build_props: a dict of build properties for the given partition.
742 prop_overrides: a set of props that are overridden by import.
743 placeholder_values: A dict of runtime variables' values to replace the
744 placeholders in the build.prop file. We expect exactly one value for
745 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000746 """
Tianjie Xu9afb2212020-05-10 21:48:15 +0000747 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000748 self.input_file = input_file
749 self.partition = name
750 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000751 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000752 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000753 self.prop_overrides = set()
754 self.placeholder_values = {}
755 if placeholder_values:
756 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000757
758 @staticmethod
759 def FromDictionary(name, build_props):
760 """Constructs an instance from a build prop dictionary."""
761
762 props = PartitionBuildProps("unknown", name)
763 props.build_props = build_props.copy()
764 return props
765
766 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000767 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000768 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000769 data = ''
770 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
771 '{}/build.prop'.format(name.upper())]:
772 try:
773 data = ReadFromInputFile(input_file, prop_file)
774 break
775 except KeyError:
776 logger.warning('Failed to read %s', prop_file)
777
Tianjie Xu9afb2212020-05-10 21:48:15 +0000778 props = PartitionBuildProps(input_file, name, placeholder_values)
779 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000780 return props
781
Tianjie Xu9afb2212020-05-10 21:48:15 +0000782 def _LoadBuildProp(self, data):
783 for line in data.split('\n'):
784 line = line.strip()
785 if not line or line.startswith("#"):
786 continue
787 if line.startswith("import"):
788 overrides = self._ImportParser(line)
789 duplicates = self.prop_overrides.intersection(overrides.keys())
790 if duplicates:
791 raise ValueError('prop {} is overridden multiple times'.format(
792 ','.join(duplicates)))
793 self.prop_overrides = self.prop_overrides.union(overrides.keys())
794 self.build_props.update(overrides)
795 elif "=" in line:
796 name, value = line.split("=", 1)
797 if name in self.prop_overrides:
798 raise ValueError('prop {} is set again after overridden by import '
799 'statement'.format(name))
800 self.build_props[name] = value
801
802 def _ImportParser(self, line):
803 """Parses the build prop in a given import statement."""
804
805 tokens = line.split()
806 if len(tokens) != 2 or tokens[0] != 'import':
807 raise ValueError('Unrecognized import statement {}'.format(line))
808 import_path = tokens[1]
809 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
810 raise ValueError('Unrecognized import path {}'.format(line))
811
812 # We only recognize a subset of import statement that the init process
813 # supports. And we can loose the restriction based on how the dynamic
814 # fingerprint is used in practice. The placeholder format should be
815 # ${placeholder}, and its value should be provided by the caller through
816 # the placeholder_values.
817 for prop, value in self.placeholder_values.items():
818 prop_place_holder = '${{{}}}'.format(prop)
819 if prop_place_holder in import_path:
820 import_path = import_path.replace(prop_place_holder, value)
821 if '$' in import_path:
822 logger.info('Unresolved place holder in import path %s', import_path)
823 return {}
824
825 import_path = import_path.replace('/{}'.format(self.partition),
826 self.partition.upper())
827 logger.info('Parsing build props override from %s', import_path)
828
829 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
830 d = LoadDictionaryFromLines(lines)
831 return {key: val for key, val in d.items()
832 if key in self.props_allow_override}
833
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000834 def GetProp(self, prop):
835 return self.build_props.get(prop)
836
837
Tianjie Xucfa86222016-03-07 16:31:19 -0800838def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
839 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700840 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800841 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700842 self.mount_point = mount_point
843 self.fs_type = fs_type
844 self.device = device
845 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700846 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700847
848 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800849 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700850 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700851 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700852 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700853
Tao Baod1de6f32017-03-01 16:38:48 -0800854 assert fstab_version == 2
855
856 d = {}
857 for line in data.split("\n"):
858 line = line.strip()
859 if not line or line.startswith("#"):
860 continue
861
862 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
863 pieces = line.split()
864 if len(pieces) != 5:
865 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
866
867 # Ignore entries that are managed by vold.
868 options = pieces[4]
869 if "voldmanaged=" in options:
870 continue
871
872 # It's a good line, parse it.
873 length = 0
874 options = options.split(",")
875 for i in options:
876 if i.startswith("length="):
877 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800878 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800879 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700880 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800881
Tao Baod1de6f32017-03-01 16:38:48 -0800882 mount_flags = pieces[3]
883 # Honor the SELinux context if present.
884 context = None
885 for i in mount_flags.split(","):
886 if i.startswith("context="):
887 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800888
Tao Baod1de6f32017-03-01 16:38:48 -0800889 mount_point = pieces[1]
890 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
891 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800892
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700893 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700894 # system. Other areas assume system is always at "/system" so point /system
895 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700896 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800897 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700898 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700899 return d
900
901
Tao Bao765668f2019-10-04 22:03:00 -0700902def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
903 """Finds the path to recovery fstab and loads its contents."""
904 # recovery fstab is only meaningful when installing an update via recovery
905 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
906 if info_dict.get('ab_update') == 'true':
907 return None
908
909 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
910 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
911 # cases, since it may load the info_dict from an old build (e.g. when
912 # generating incremental OTAs from that build).
913 system_root_image = info_dict.get('system_root_image') == 'true'
914 if info_dict.get('no_recovery') != 'true':
915 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
916 if isinstance(input_file, zipfile.ZipFile):
917 if recovery_fstab_path not in input_file.namelist():
918 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
919 else:
920 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
921 if not os.path.exists(path):
922 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
923 return LoadRecoveryFSTab(
924 read_helper, info_dict['fstab_version'], recovery_fstab_path,
925 system_root_image)
926
927 if info_dict.get('recovery_as_boot') == 'true':
928 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
929 if isinstance(input_file, zipfile.ZipFile):
930 if recovery_fstab_path not in input_file.namelist():
931 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
932 else:
933 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
934 if not os.path.exists(path):
935 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
936 return LoadRecoveryFSTab(
937 read_helper, info_dict['fstab_version'], recovery_fstab_path,
938 system_root_image)
939
940 return None
941
942
Doug Zongker37974732010-09-16 17:44:38 -0700943def DumpInfoDict(d):
944 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700945 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700946
Dan Albert8b72aef2015-03-23 19:13:21 -0700947
Daniel Norman55417142019-11-25 16:04:36 -0800948def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700949 """Merges dynamic partition info variables.
950
951 Args:
952 framework_dict: The dictionary of dynamic partition info variables from the
953 partial framework target files.
954 vendor_dict: The dictionary of dynamic partition info variables from the
955 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700956
957 Returns:
958 The merged dynamic partition info dictionary.
959 """
960 merged_dict = {}
961 # Partition groups and group sizes are defined by the vendor dict because
962 # these values may vary for each board that uses a shared system image.
963 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800964 framework_dynamic_partition_list = framework_dict.get(
965 "dynamic_partition_list", "")
966 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
967 merged_dict["dynamic_partition_list"] = ("%s %s" % (
968 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700969 for partition_group in merged_dict["super_partition_groups"].split(" "):
970 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800971 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700972 if key not in vendor_dict:
973 raise ValueError("Vendor dict does not contain required key %s." % key)
974 merged_dict[key] = vendor_dict[key]
975
976 # Set the partition group's partition list using a concatenation of the
977 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800978 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700979 merged_dict[key] = (
980 "%s %s" %
981 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530982
983 # Pick virtual ab related flags from vendor dict, if defined.
984 if "virtual_ab" in vendor_dict.keys():
985 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
986 if "virtual_ab_retrofit" in vendor_dict.keys():
987 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700988 return merged_dict
989
990
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800991def AppendAVBSigningArgs(cmd, partition):
992 """Append signing arguments for avbtool."""
993 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
994 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700995 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
996 new_key_path = os.path.join(OPTIONS.search_path, key_path)
997 if os.path.exists(new_key_path):
998 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800999 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1000 if key_path and algorithm:
1001 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001002 avb_salt = OPTIONS.info_dict.get("avb_salt")
1003 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001004 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001005 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001006
1007
Tao Bao765668f2019-10-04 22:03:00 -07001008def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001009 """Returns the VBMeta arguments for partition.
1010
1011 It sets up the VBMeta argument by including the partition descriptor from the
1012 given 'image', or by configuring the partition as a chained partition.
1013
1014 Args:
1015 partition: The name of the partition (e.g. "system").
1016 image: The path to the partition image.
1017 info_dict: A dict returned by common.LoadInfoDict(). Will use
1018 OPTIONS.info_dict if None has been given.
1019
1020 Returns:
1021 A list of VBMeta arguments.
1022 """
1023 if info_dict is None:
1024 info_dict = OPTIONS.info_dict
1025
1026 # Check if chain partition is used.
1027 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001028 if not key_path:
1029 return ["--include_descriptors_from_image", image]
1030
1031 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1032 # into vbmeta.img. The recovery image will be configured on an independent
1033 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1034 # See details at
1035 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001036 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001037 return []
1038
1039 # Otherwise chain the partition into vbmeta.
1040 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1041 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001042
1043
Tao Bao02a08592018-07-22 12:40:45 -07001044def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1045 """Constructs and returns the arg to build or verify a chained partition.
1046
1047 Args:
1048 partition: The partition name.
1049 info_dict: The info dict to look up the key info and rollback index
1050 location.
1051 key: The key to be used for building or verifying the partition. Defaults to
1052 the key listed in info_dict.
1053
1054 Returns:
1055 A string of form "partition:rollback_index_location:key" that can be used to
1056 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001057 """
1058 if key is None:
1059 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001060 if key and not os.path.exists(key) and OPTIONS.search_path:
1061 new_key_path = os.path.join(OPTIONS.search_path, key)
1062 if os.path.exists(new_key_path):
1063 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001064 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001065 rollback_index_location = info_dict[
1066 "avb_" + partition + "_rollback_index_location"]
1067 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1068
1069
Tianjie20dd8f22020-04-19 15:51:16 -07001070def ConstructAftlMakeImageCommands(output_image):
1071 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001072
1073 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001074 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001075 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1076 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1077 'No AFTL manufacturer key provided.'
1078
1079 vbmeta_image = MakeTempFile()
1080 os.rename(output_image, vbmeta_image)
1081 build_info = BuildInfo(OPTIONS.info_dict)
1082 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001083 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001084 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001085 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001086 "--vbmeta_image_path", vbmeta_image,
1087 "--output", output_image,
1088 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001089 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001090 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1091 "--algorithm", "SHA256_RSA4096",
1092 "--padding", "4096"]
1093 if OPTIONS.aftl_signer_helper:
1094 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001095 return aftl_cmd
1096
1097
1098def AddAftlInclusionProof(output_image):
1099 """Appends the aftl inclusion proof to the vbmeta image."""
1100
1101 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001102 RunAndCheckOutput(aftl_cmd)
1103
1104 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1105 output_image, '--transparency_log_pub_keys',
1106 OPTIONS.aftl_key_path]
1107 RunAndCheckOutput(verify_cmd)
1108
1109
Daniel Norman276f0622019-07-26 14:13:51 -07001110def BuildVBMeta(image_path, partitions, name, needed_partitions):
1111 """Creates a VBMeta image.
1112
1113 It generates the requested VBMeta image. The requested image could be for
1114 top-level or chained VBMeta image, which is determined based on the name.
1115
1116 Args:
1117 image_path: The output path for the new VBMeta image.
1118 partitions: A dict that's keyed by partition names with image paths as
1119 values. Only valid partition names are accepted, as listed in
1120 common.AVB_PARTITIONS.
1121 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1122 needed_partitions: Partitions whose descriptors should be included into the
1123 generated VBMeta image.
1124
1125 Raises:
1126 AssertionError: On invalid input args.
1127 """
1128 avbtool = OPTIONS.info_dict["avb_avbtool"]
1129 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1130 AppendAVBSigningArgs(cmd, name)
1131
1132 for partition, path in partitions.items():
1133 if partition not in needed_partitions:
1134 continue
1135 assert (partition in AVB_PARTITIONS or
1136 partition in AVB_VBMETA_PARTITIONS), \
1137 'Unknown partition: {}'.format(partition)
1138 assert os.path.exists(path), \
1139 'Failed to find {} for {}'.format(path, partition)
1140 cmd.extend(GetAvbPartitionArg(partition, path))
1141
1142 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1143 if args and args.strip():
1144 split_args = shlex.split(args)
1145 for index, arg in enumerate(split_args[:-1]):
1146 # Sanity check that the image file exists. Some images might be defined
1147 # as a path relative to source tree, which may not be available at the
1148 # same location when running this script (we have the input target_files
1149 # zip only). For such cases, we additionally scan other locations (e.g.
1150 # IMAGES/, RADIO/, etc) before bailing out.
1151 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001152 chained_image = split_args[index + 1]
1153 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001154 continue
1155 found = False
1156 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1157 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001158 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001159 if os.path.exists(alt_path):
1160 split_args[index + 1] = alt_path
1161 found = True
1162 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001163 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001164 cmd.extend(split_args)
1165
1166 RunAndCheckOutput(cmd)
1167
Tianjie Xueaed60c2020-03-12 00:33:28 -07001168 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001169 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001170 AddAftlInclusionProof(image_path)
1171
Daniel Norman276f0622019-07-26 14:13:51 -07001172
Steve Mucklee1b10862019-07-10 10:49:37 -07001173def _MakeRamdisk(sourcedir, fs_config_file=None):
1174 ramdisk_img = tempfile.NamedTemporaryFile()
1175
1176 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1177 cmd = ["mkbootfs", "-f", fs_config_file,
1178 os.path.join(sourcedir, "RAMDISK")]
1179 else:
1180 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1181 p1 = Run(cmd, stdout=subprocess.PIPE)
1182 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1183
1184 p2.wait()
1185 p1.wait()
1186 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1187 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1188
1189 return ramdisk_img
1190
1191
Steve Muckle9793cf62020-04-08 18:27:00 -07001192def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001193 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001194 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001195
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001196 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001197 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1198 we are building a two-step special image (i.e. building a recovery image to
1199 be loaded into /boot in two-step OTAs).
1200
1201 Return the image data, or None if sourcedir does not appear to contains files
1202 for building the requested image.
1203 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001204
Steve Muckle9793cf62020-04-08 18:27:00 -07001205 # "boot" or "recovery", without extension.
1206 partition_name = os.path.basename(sourcedir).lower()
1207
1208 if partition_name == "recovery":
1209 kernel = "kernel"
1210 else:
1211 kernel = image_name.replace("boot", "kernel")
1212 kernel = kernel.replace(".img","")
1213 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001214 return None
1215
1216 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001217 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001218
Doug Zongkerd5131602012-08-02 14:46:42 -07001219 if info_dict is None:
1220 info_dict = OPTIONS.info_dict
1221
Doug Zongkereef39442009-04-02 12:14:19 -07001222 img = tempfile.NamedTemporaryFile()
1223
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001224 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001225 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001226
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001227 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1228 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1229
Steve Muckle9793cf62020-04-08 18:27:00 -07001230 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001231
Benoit Fradina45a8682014-07-14 21:00:43 +02001232 fn = os.path.join(sourcedir, "second")
1233 if os.access(fn, os.F_OK):
1234 cmd.append("--second")
1235 cmd.append(fn)
1236
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001237 fn = os.path.join(sourcedir, "dtb")
1238 if os.access(fn, os.F_OK):
1239 cmd.append("--dtb")
1240 cmd.append(fn)
1241
Doug Zongker171f1cd2009-06-15 22:36:37 -07001242 fn = os.path.join(sourcedir, "cmdline")
1243 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001244 cmd.append("--cmdline")
1245 cmd.append(open(fn).read().rstrip("\n"))
1246
1247 fn = os.path.join(sourcedir, "base")
1248 if os.access(fn, os.F_OK):
1249 cmd.append("--base")
1250 cmd.append(open(fn).read().rstrip("\n"))
1251
Ying Wang4de6b5b2010-08-25 14:29:34 -07001252 fn = os.path.join(sourcedir, "pagesize")
1253 if os.access(fn, os.F_OK):
1254 cmd.append("--pagesize")
1255 cmd.append(open(fn).read().rstrip("\n"))
1256
Steve Mucklef84668e2020-03-16 19:13:46 -07001257 if partition_name == "recovery":
1258 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301259 if not args:
1260 # Fall back to "mkbootimg_args" for recovery image
1261 # in case "recovery_mkbootimg_args" is not set.
1262 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001263 else:
1264 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001265 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001266 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001267
Tao Bao76def242017-11-21 09:25:31 -08001268 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001269 if args and args.strip():
1270 cmd.extend(shlex.split(args))
1271
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001272 if has_ramdisk:
1273 cmd.extend(["--ramdisk", ramdisk_img.name])
1274
Tao Baod95e9fd2015-03-29 23:07:41 -07001275 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001276 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001277 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001278 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001279 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001280 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001281
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001282 if partition_name == "recovery":
1283 if info_dict.get("include_recovery_dtbo") == "true":
1284 fn = os.path.join(sourcedir, "recovery_dtbo")
1285 cmd.extend(["--recovery_dtbo", fn])
1286 if info_dict.get("include_recovery_acpio") == "true":
1287 fn = os.path.join(sourcedir, "recovery_acpio")
1288 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001289
Tao Bao986ee862018-10-04 15:46:16 -07001290 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001291
Tao Bao76def242017-11-21 09:25:31 -08001292 if (info_dict.get("boot_signer") == "true" and
1293 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001294 # Hard-code the path as "/boot" for two-step special recovery image (which
1295 # will be loaded into /boot during the two-step OTA).
1296 if two_step_image:
1297 path = "/boot"
1298 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001299 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001300 cmd = [OPTIONS.boot_signer_path]
1301 cmd.extend(OPTIONS.boot_signer_args)
1302 cmd.extend([path, img.name,
1303 info_dict["verity_key"] + ".pk8",
1304 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001305 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001306
Tao Baod95e9fd2015-03-29 23:07:41 -07001307 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001308 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001309 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001310 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001311 # We have switched from the prebuilt futility binary to using the tool
1312 # (futility-host) built from the source. Override the setting in the old
1313 # TF.zip.
1314 futility = info_dict["futility"]
1315 if futility.startswith("prebuilts/"):
1316 futility = "futility-host"
1317 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001318 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001319 info_dict["vboot_key"] + ".vbprivk",
1320 info_dict["vboot_subkey"] + ".vbprivk",
1321 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001322 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001323 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001324
Tao Baof3282b42015-04-01 11:21:55 -07001325 # Clean up the temp files.
1326 img_unsigned.close()
1327 img_keyblock.close()
1328
David Zeuthen8fecb282017-12-01 16:24:01 -05001329 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001330 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001331 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001332 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001333 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001334 "--partition_size", str(part_size), "--partition_name",
1335 partition_name]
1336 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001337 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001338 if args and args.strip():
1339 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001340 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001341
1342 img.seek(os.SEEK_SET, 0)
1343 data = img.read()
1344
1345 if has_ramdisk:
1346 ramdisk_img.close()
1347 img.close()
1348
1349 return data
1350
1351
Doug Zongkerd5131602012-08-02 14:46:42 -07001352def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001353 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001354 """Return a File object with the desired bootable image.
1355
1356 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1357 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1358 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001359
Doug Zongker55d93282011-01-25 17:03:34 -08001360 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1361 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001362 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001363 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001364
1365 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1366 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001367 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001368 return File.FromLocalFile(name, prebuilt_path)
1369
Tao Bao32fcdab2018-10-12 10:30:39 -07001370 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001371
1372 if info_dict is None:
1373 info_dict = OPTIONS.info_dict
1374
1375 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001376 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1377 # for recovery.
1378 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1379 prebuilt_name != "boot.img" or
1380 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001381
Doug Zongker6f1d0312014-08-22 08:07:12 -07001382 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001383 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001384 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001385 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001386 if data:
1387 return File(name, data)
1388 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001389
Doug Zongkereef39442009-04-02 12:14:19 -07001390
Steve Mucklee1b10862019-07-10 10:49:37 -07001391def _BuildVendorBootImage(sourcedir, info_dict=None):
1392 """Build a vendor boot image from the specified sourcedir.
1393
1394 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1395 turn them into a vendor boot image.
1396
1397 Return the image data, or None if sourcedir does not appear to contains files
1398 for building the requested image.
1399 """
1400
1401 if info_dict is None:
1402 info_dict = OPTIONS.info_dict
1403
1404 img = tempfile.NamedTemporaryFile()
1405
1406 ramdisk_img = _MakeRamdisk(sourcedir)
1407
1408 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1409 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1410
1411 cmd = [mkbootimg]
1412
1413 fn = os.path.join(sourcedir, "dtb")
1414 if os.access(fn, os.F_OK):
1415 cmd.append("--dtb")
1416 cmd.append(fn)
1417
1418 fn = os.path.join(sourcedir, "vendor_cmdline")
1419 if os.access(fn, os.F_OK):
1420 cmd.append("--vendor_cmdline")
1421 cmd.append(open(fn).read().rstrip("\n"))
1422
1423 fn = os.path.join(sourcedir, "base")
1424 if os.access(fn, os.F_OK):
1425 cmd.append("--base")
1426 cmd.append(open(fn).read().rstrip("\n"))
1427
1428 fn = os.path.join(sourcedir, "pagesize")
1429 if os.access(fn, os.F_OK):
1430 cmd.append("--pagesize")
1431 cmd.append(open(fn).read().rstrip("\n"))
1432
1433 args = info_dict.get("mkbootimg_args")
1434 if args and args.strip():
1435 cmd.extend(shlex.split(args))
1436
1437 args = info_dict.get("mkbootimg_version_args")
1438 if args and args.strip():
1439 cmd.extend(shlex.split(args))
1440
1441 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1442 cmd.extend(["--vendor_boot", img.name])
1443
1444 RunAndCheckOutput(cmd)
1445
1446 # AVB: if enabled, calculate and add hash.
1447 if info_dict.get("avb_enable") == "true":
1448 avbtool = info_dict["avb_avbtool"]
1449 part_size = info_dict["vendor_boot_size"]
1450 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001451 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001452 AppendAVBSigningArgs(cmd, "vendor_boot")
1453 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1454 if args and args.strip():
1455 cmd.extend(shlex.split(args))
1456 RunAndCheckOutput(cmd)
1457
1458 img.seek(os.SEEK_SET, 0)
1459 data = img.read()
1460
1461 ramdisk_img.close()
1462 img.close()
1463
1464 return data
1465
1466
1467def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1468 info_dict=None):
1469 """Return a File object with the desired vendor boot image.
1470
1471 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1472 the source files in 'unpack_dir'/'tree_subdir'."""
1473
1474 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1475 if os.path.exists(prebuilt_path):
1476 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1477 return File.FromLocalFile(name, prebuilt_path)
1478
1479 logger.info("building image from target_files %s...", tree_subdir)
1480
1481 if info_dict is None:
1482 info_dict = OPTIONS.info_dict
1483
1484 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1485 if data:
1486 return File(name, data)
1487 return None
1488
1489
Narayan Kamatha07bf042017-08-14 14:49:21 +01001490def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001491 """Gunzips the given gzip compressed file to a given output file."""
1492 with gzip.open(in_filename, "rb") as in_file, \
1493 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001494 shutil.copyfileobj(in_file, out_file)
1495
1496
Tao Bao0ff15de2019-03-20 11:26:06 -07001497def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001498 """Unzips the archive to the given directory.
1499
1500 Args:
1501 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001502 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001503 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1504 archvie. Non-matching patterns will be filtered out. If there's no match
1505 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001506 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001507 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001508 if patterns is not None:
1509 # Filter out non-matching patterns. unzip will complain otherwise.
1510 with zipfile.ZipFile(filename) as input_zip:
1511 names = input_zip.namelist()
1512 filtered = [
1513 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1514
1515 # There isn't any matching files. Don't unzip anything.
1516 if not filtered:
1517 return
1518 cmd.extend(filtered)
1519
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001520 RunAndCheckOutput(cmd)
1521
1522
Doug Zongker75f17362009-12-08 13:46:44 -08001523def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001524 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001525
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001526 Args:
1527 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1528 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1529
1530 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1531 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001532
Tao Bao1c830bf2017-12-25 10:43:47 -08001533 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001534 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001535 """
Doug Zongkereef39442009-04-02 12:14:19 -07001536
Tao Bao1c830bf2017-12-25 10:43:47 -08001537 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001538 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1539 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001540 UnzipToDir(m.group(1), tmp, pattern)
1541 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001542 filename = m.group(1)
1543 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001544 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001545
Tao Baodba59ee2018-01-09 13:21:02 -08001546 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001547
1548
Yifan Hong8a66a712019-04-04 15:37:57 -07001549def GetUserImage(which, tmpdir, input_zip,
1550 info_dict=None,
1551 allow_shared_blocks=None,
1552 hashtree_info_generator=None,
1553 reset_file_map=False):
1554 """Returns an Image object suitable for passing to BlockImageDiff.
1555
1556 This function loads the specified image from the given path. If the specified
1557 image is sparse, it also performs additional processing for OTA purpose. For
1558 example, it always adds block 0 to clobbered blocks list. It also detects
1559 files that cannot be reconstructed from the block list, for whom we should
1560 avoid applying imgdiff.
1561
1562 Args:
1563 which: The partition name.
1564 tmpdir: The directory that contains the prebuilt image and block map file.
1565 input_zip: The target-files ZIP archive.
1566 info_dict: The dict to be looked up for relevant info.
1567 allow_shared_blocks: If image is sparse, whether having shared blocks is
1568 allowed. If none, it is looked up from info_dict.
1569 hashtree_info_generator: If present and image is sparse, generates the
1570 hashtree_info for this sparse image.
1571 reset_file_map: If true and image is sparse, reset file map before returning
1572 the image.
1573 Returns:
1574 A Image object. If it is a sparse image and reset_file_map is False, the
1575 image will have file_map info loaded.
1576 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001577 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001578 info_dict = LoadInfoDict(input_zip)
1579
1580 is_sparse = info_dict.get("extfs_sparse_flag")
1581
1582 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1583 # shared blocks (i.e. some blocks will show up in multiple files' block
1584 # list). We can only allocate such shared blocks to the first "owner", and
1585 # disable imgdiff for all later occurrences.
1586 if allow_shared_blocks is None:
1587 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1588
1589 if is_sparse:
1590 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1591 hashtree_info_generator)
1592 if reset_file_map:
1593 img.ResetFileMap()
1594 return img
1595 else:
1596 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1597
1598
1599def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1600 """Returns a Image object suitable for passing to BlockImageDiff.
1601
1602 This function loads the specified non-sparse image from the given path.
1603
1604 Args:
1605 which: The partition name.
1606 tmpdir: The directory that contains the prebuilt image and block map file.
1607 Returns:
1608 A Image object.
1609 """
1610 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1611 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1612
1613 # The image and map files must have been created prior to calling
1614 # ota_from_target_files.py (since LMP).
1615 assert os.path.exists(path) and os.path.exists(mappath)
1616
Tianjie Xu41976c72019-07-03 13:57:01 -07001617 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1618
Yifan Hong8a66a712019-04-04 15:37:57 -07001619
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001620def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1621 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001622 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1623
1624 This function loads the specified sparse image from the given path, and
1625 performs additional processing for OTA purpose. For example, it always adds
1626 block 0 to clobbered blocks list. It also detects files that cannot be
1627 reconstructed from the block list, for whom we should avoid applying imgdiff.
1628
1629 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001630 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001631 tmpdir: The directory that contains the prebuilt image and block map file.
1632 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001633 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001634 hashtree_info_generator: If present, generates the hashtree_info for this
1635 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001636 Returns:
1637 A SparseImage object, with file_map info loaded.
1638 """
Tao Baoc765cca2018-01-31 17:32:40 -08001639 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1640 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1641
1642 # The image and map files must have been created prior to calling
1643 # ota_from_target_files.py (since LMP).
1644 assert os.path.exists(path) and os.path.exists(mappath)
1645
1646 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1647 # it to clobbered_blocks so that it will be written to the target
1648 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1649 clobbered_blocks = "0"
1650
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001651 image = sparse_img.SparseImage(
1652 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1653 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001654
1655 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1656 # if they contain all zeros. We can't reconstruct such a file from its block
1657 # list. Tag such entries accordingly. (Bug: 65213616)
1658 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001659 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001660 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001661 continue
1662
Tom Cherryd14b8952018-08-09 14:26:00 -07001663 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1664 # filename listed in system.map may contain an additional leading slash
1665 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1666 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001667 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001668
Tom Cherryd14b8952018-08-09 14:26:00 -07001669 # Special handling another case, where files not under /system
1670 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001671 if which == 'system' and not arcname.startswith('SYSTEM'):
1672 arcname = 'ROOT/' + arcname
1673
1674 assert arcname in input_zip.namelist(), \
1675 "Failed to find the ZIP entry for {}".format(entry)
1676
Tao Baoc765cca2018-01-31 17:32:40 -08001677 info = input_zip.getinfo(arcname)
1678 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001679
1680 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001681 # image, check the original block list to determine its completeness. Note
1682 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001683 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001684 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001685
Tao Baoc765cca2018-01-31 17:32:40 -08001686 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1687 ranges.extra['incomplete'] = True
1688
1689 return image
1690
1691
Doug Zongkereef39442009-04-02 12:14:19 -07001692def GetKeyPasswords(keylist):
1693 """Given a list of keys, prompt the user to enter passwords for
1694 those which require them. Return a {key: password} dict. password
1695 will be None if the key has no password."""
1696
Doug Zongker8ce7c252009-05-22 13:34:54 -07001697 no_passwords = []
1698 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001699 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001700 devnull = open("/dev/null", "w+b")
1701 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001702 # We don't need a password for things that aren't really keys.
1703 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001704 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001705 continue
1706
T.R. Fullhart37e10522013-03-18 10:31:26 -07001707 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001708 "-inform", "DER", "-nocrypt"],
1709 stdin=devnull.fileno(),
1710 stdout=devnull.fileno(),
1711 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001712 p.communicate()
1713 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001714 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001715 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001716 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001717 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1718 "-inform", "DER", "-passin", "pass:"],
1719 stdin=devnull.fileno(),
1720 stdout=devnull.fileno(),
1721 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001722 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001723 if p.returncode == 0:
1724 # Encrypted key with empty string as password.
1725 key_passwords[k] = ''
1726 elif stderr.startswith('Error decrypting key'):
1727 # Definitely encrypted key.
1728 # It would have said "Error reading key" if it didn't parse correctly.
1729 need_passwords.append(k)
1730 else:
1731 # Potentially, a type of key that openssl doesn't understand.
1732 # We'll let the routines in signapk.jar handle it.
1733 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001734 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001735
T.R. Fullhart37e10522013-03-18 10:31:26 -07001736 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001737 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001738 return key_passwords
1739
1740
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001741def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001742 """Gets the minSdkVersion declared in the APK.
1743
changho.shin0f125362019-07-08 10:59:00 +09001744 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001745 This can be both a decimal number (API Level) or a codename.
1746
1747 Args:
1748 apk_name: The APK filename.
1749
1750 Returns:
1751 The parsed SDK version string.
1752
1753 Raises:
1754 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001755 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001756 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001757 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001758 stderr=subprocess.PIPE)
1759 stdoutdata, stderrdata = proc.communicate()
1760 if proc.returncode != 0:
1761 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001762 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001763 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001764
Tao Baof47bf0f2018-03-21 23:28:51 -07001765 for line in stdoutdata.split("\n"):
1766 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001767 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1768 if m:
1769 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001770 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001771
1772
1773def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001774 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001775
Tao Baof47bf0f2018-03-21 23:28:51 -07001776 If minSdkVersion is set to a codename, it is translated to a number using the
1777 provided map.
1778
1779 Args:
1780 apk_name: The APK filename.
1781
1782 Returns:
1783 The parsed SDK version number.
1784
1785 Raises:
1786 ExternalError: On failing to get the min SDK version number.
1787 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001788 version = GetMinSdkVersion(apk_name)
1789 try:
1790 return int(version)
1791 except ValueError:
1792 # Not a decimal number. Codename?
1793 if version in codename_to_api_level_map:
1794 return codename_to_api_level_map[version]
1795 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001796 raise ExternalError(
1797 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1798 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001799
1800
1801def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001802 codename_to_api_level_map=None, whole_file=False,
1803 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001804 """Sign the input_name zip/jar/apk, producing output_name. Use the
1805 given key and password (the latter may be None if the key does not
1806 have a password.
1807
Doug Zongker951495f2009-08-14 12:44:19 -07001808 If whole_file is true, use the "-w" option to SignApk to embed a
1809 signature that covers the whole file in the archive comment of the
1810 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001811
1812 min_api_level is the API Level (int) of the oldest platform this file may end
1813 up on. If not specified for an APK, the API Level is obtained by interpreting
1814 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1815
1816 codename_to_api_level_map is needed to translate the codename which may be
1817 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001818
1819 Caller may optionally specify extra args to be passed to SignApk, which
1820 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001821 """
Tao Bao76def242017-11-21 09:25:31 -08001822 if codename_to_api_level_map is None:
1823 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001824 if extra_signapk_args is None:
1825 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001826
Alex Klyubin9667b182015-12-10 13:38:50 -08001827 java_library_path = os.path.join(
1828 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1829
Tao Baoe95540e2016-11-08 12:08:53 -08001830 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1831 ["-Djava.library.path=" + java_library_path,
1832 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001833 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001834 if whole_file:
1835 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001836
1837 min_sdk_version = min_api_level
1838 if min_sdk_version is None:
1839 if not whole_file:
1840 min_sdk_version = GetMinSdkVersionInt(
1841 input_name, codename_to_api_level_map)
1842 if min_sdk_version is not None:
1843 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1844
T.R. Fullhart37e10522013-03-18 10:31:26 -07001845 cmd.extend([key + OPTIONS.public_key_suffix,
1846 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001847 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001848
Tao Bao73dd4f42018-10-04 16:25:33 -07001849 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001850 if password is not None:
1851 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001852 stdoutdata, _ = proc.communicate(password)
1853 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001854 raise ExternalError(
1855 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001856 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001857
Doug Zongkereef39442009-04-02 12:14:19 -07001858
Doug Zongker37974732010-09-16 17:44:38 -07001859def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001860 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001861
Tao Bao9dd909e2017-11-14 11:27:32 -08001862 For non-AVB images, raise exception if the data is too big. Print a warning
1863 if the data is nearing the maximum size.
1864
1865 For AVB images, the actual image size should be identical to the limit.
1866
1867 Args:
1868 data: A string that contains all the data for the partition.
1869 target: The partition name. The ".img" suffix is optional.
1870 info_dict: The dict to be looked up for relevant info.
1871 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001872 if target.endswith(".img"):
1873 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001874 mount_point = "/" + target
1875
Ying Wangf8824af2014-06-03 14:07:27 -07001876 fs_type = None
1877 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001878 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001879 if mount_point == "/userdata":
1880 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001881 p = info_dict["fstab"][mount_point]
1882 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001883 device = p.device
1884 if "/" in device:
1885 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001886 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001887 if not fs_type or not limit:
1888 return
Doug Zongkereef39442009-04-02 12:14:19 -07001889
Andrew Boie0f9aec82012-02-14 09:32:52 -08001890 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001891 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1892 # path.
1893 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1894 if size != limit:
1895 raise ExternalError(
1896 "Mismatching image size for %s: expected %d actual %d" % (
1897 target, limit, size))
1898 else:
1899 pct = float(size) * 100.0 / limit
1900 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1901 if pct >= 99.0:
1902 raise ExternalError(msg)
1903 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001904 logger.warning("\n WARNING: %s\n", msg)
1905 else:
1906 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001907
1908
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001909def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001910 """Parses the APK certs info from a given target-files zip.
1911
1912 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1913 tuple with the following elements: (1) a dictionary that maps packages to
1914 certs (based on the "certificate" and "private_key" attributes in the file;
1915 (2) a string representing the extension of compressed APKs in the target files
1916 (e.g ".gz", ".bro").
1917
1918 Args:
1919 tf_zip: The input target_files ZipFile (already open).
1920
1921 Returns:
1922 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1923 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1924 no compressed APKs.
1925 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001926 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001927 compressed_extension = None
1928
Tao Bao0f990332017-09-08 19:02:54 -07001929 # META/apkcerts.txt contains the info for _all_ the packages known at build
1930 # time. Filter out the ones that are not installed.
1931 installed_files = set()
1932 for name in tf_zip.namelist():
1933 basename = os.path.basename(name)
1934 if basename:
1935 installed_files.add(basename)
1936
Tao Baoda30cfa2017-12-01 16:19:46 -08001937 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001938 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001939 if not line:
1940 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001941 m = re.match(
1942 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001943 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1944 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001945 line)
1946 if not m:
1947 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001948
Tao Bao818ddf52018-01-05 11:17:34 -08001949 matches = m.groupdict()
1950 cert = matches["CERT"]
1951 privkey = matches["PRIVKEY"]
1952 name = matches["NAME"]
1953 this_compressed_extension = matches["COMPRESSED"]
1954
1955 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1956 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1957 if cert in SPECIAL_CERT_STRINGS and not privkey:
1958 certmap[name] = cert
1959 elif (cert.endswith(OPTIONS.public_key_suffix) and
1960 privkey.endswith(OPTIONS.private_key_suffix) and
1961 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1962 certmap[name] = cert[:-public_key_suffix_len]
1963 else:
1964 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1965
1966 if not this_compressed_extension:
1967 continue
1968
1969 # Only count the installed files.
1970 filename = name + '.' + this_compressed_extension
1971 if filename not in installed_files:
1972 continue
1973
1974 # Make sure that all the values in the compression map have the same
1975 # extension. We don't support multiple compression methods in the same
1976 # system image.
1977 if compressed_extension:
1978 if this_compressed_extension != compressed_extension:
1979 raise ValueError(
1980 "Multiple compressed extensions: {} vs {}".format(
1981 compressed_extension, this_compressed_extension))
1982 else:
1983 compressed_extension = this_compressed_extension
1984
1985 return (certmap,
1986 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001987
1988
Doug Zongkereef39442009-04-02 12:14:19 -07001989COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001990Global options
1991
1992 -p (--path) <dir>
1993 Prepend <dir>/bin to the list of places to search for binaries run by this
1994 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001995
Doug Zongker05d3dea2009-06-22 11:32:31 -07001996 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001997 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001998
Tao Bao30df8b42018-04-23 15:32:53 -07001999 -x (--extra) <key=value>
2000 Add a key/value pair to the 'extras' dict, which device-specific extension
2001 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002002
Doug Zongkereef39442009-04-02 12:14:19 -07002003 -v (--verbose)
2004 Show command lines being executed.
2005
2006 -h (--help)
2007 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002008
2009 --logfile <file>
2010 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002011"""
2012
2013def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002014 print(docstring.rstrip("\n"))
2015 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002016
2017
2018def ParseOptions(argv,
2019 docstring,
2020 extra_opts="", extra_long_opts=(),
2021 extra_option_handler=None):
2022 """Parse the options in argv and return any arguments that aren't
2023 flags. docstring is the calling module's docstring, to be displayed
2024 for errors and -h. extra_opts and extra_long_opts are for flags
2025 defined by the caller, which are processed by passing them to
2026 extra_option_handler."""
2027
2028 try:
2029 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002030 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002031 ["help", "verbose", "path=", "signapk_path=",
2032 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002033 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002034 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2035 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002036 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2037 "aftl_key_path=", "aftl_manufacturer_key_path=",
2038 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002039 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002040 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002041 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002042 sys.exit(2)
2043
Doug Zongkereef39442009-04-02 12:14:19 -07002044 for o, a in opts:
2045 if o in ("-h", "--help"):
2046 Usage(docstring)
2047 sys.exit()
2048 elif o in ("-v", "--verbose"):
2049 OPTIONS.verbose = True
2050 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002051 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002052 elif o in ("--signapk_path",):
2053 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002054 elif o in ("--signapk_shared_library_path",):
2055 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002056 elif o in ("--extra_signapk_args",):
2057 OPTIONS.extra_signapk_args = shlex.split(a)
2058 elif o in ("--java_path",):
2059 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002060 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002061 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002062 elif o in ("--android_jar_path",):
2063 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002064 elif o in ("--public_key_suffix",):
2065 OPTIONS.public_key_suffix = a
2066 elif o in ("--private_key_suffix",):
2067 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002068 elif o in ("--boot_signer_path",):
2069 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002070 elif o in ("--boot_signer_args",):
2071 OPTIONS.boot_signer_args = shlex.split(a)
2072 elif o in ("--verity_signer_path",):
2073 OPTIONS.verity_signer_path = a
2074 elif o in ("--verity_signer_args",):
2075 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002076 elif o in ("--aftl_tool_path",):
2077 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002078 elif o in ("--aftl_server",):
2079 OPTIONS.aftl_server = a
2080 elif o in ("--aftl_key_path",):
2081 OPTIONS.aftl_key_path = a
2082 elif o in ("--aftl_manufacturer_key_path",):
2083 OPTIONS.aftl_manufacturer_key_path = a
2084 elif o in ("--aftl_signer_helper",):
2085 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002086 elif o in ("-s", "--device_specific"):
2087 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002088 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002089 key, value = a.split("=", 1)
2090 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002091 elif o in ("--logfile",):
2092 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002093 else:
2094 if extra_option_handler is None or not extra_option_handler(o, a):
2095 assert False, "unknown option \"%s\"" % (o,)
2096
Doug Zongker85448772014-09-09 14:59:20 -07002097 if OPTIONS.search_path:
2098 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2099 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002100
2101 return args
2102
2103
Tao Bao4c851b12016-09-19 13:54:38 -07002104def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002105 """Make a temp file and add it to the list of things to be deleted
2106 when Cleanup() is called. Return the filename."""
2107 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2108 os.close(fd)
2109 OPTIONS.tempfiles.append(fn)
2110 return fn
2111
2112
Tao Bao1c830bf2017-12-25 10:43:47 -08002113def MakeTempDir(prefix='tmp', suffix=''):
2114 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2115
2116 Returns:
2117 The absolute pathname of the new directory.
2118 """
2119 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2120 OPTIONS.tempfiles.append(dir_name)
2121 return dir_name
2122
2123
Doug Zongkereef39442009-04-02 12:14:19 -07002124def Cleanup():
2125 for i in OPTIONS.tempfiles:
2126 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002127 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002128 else:
2129 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002130 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002131
2132
2133class PasswordManager(object):
2134 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002135 self.editor = os.getenv("EDITOR")
2136 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002137
2138 def GetPasswords(self, items):
2139 """Get passwords corresponding to each string in 'items',
2140 returning a dict. (The dict may have keys in addition to the
2141 values in 'items'.)
2142
2143 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2144 user edit that file to add more needed passwords. If no editor is
2145 available, or $ANDROID_PW_FILE isn't define, prompts the user
2146 interactively in the ordinary way.
2147 """
2148
2149 current = self.ReadFile()
2150
2151 first = True
2152 while True:
2153 missing = []
2154 for i in items:
2155 if i not in current or not current[i]:
2156 missing.append(i)
2157 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002158 if not missing:
2159 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002160
2161 for i in missing:
2162 current[i] = ""
2163
2164 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002165 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002166 if sys.version_info[0] >= 3:
2167 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002168 answer = raw_input("try to edit again? [y]> ").strip()
2169 if answer and answer[0] not in 'yY':
2170 raise RuntimeError("key passwords unavailable")
2171 first = False
2172
2173 current = self.UpdateAndReadFile(current)
2174
Dan Albert8b72aef2015-03-23 19:13:21 -07002175 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002176 """Prompt the user to enter a value (password) for each key in
2177 'current' whose value is fales. Returns a new dict with all the
2178 values.
2179 """
2180 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002181 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002182 if v:
2183 result[k] = v
2184 else:
2185 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002186 result[k] = getpass.getpass(
2187 "Enter password for %s key> " % k).strip()
2188 if result[k]:
2189 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002190 return result
2191
2192 def UpdateAndReadFile(self, current):
2193 if not self.editor or not self.pwfile:
2194 return self.PromptResult(current)
2195
2196 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002197 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002198 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2199 f.write("# (Additional spaces are harmless.)\n\n")
2200
2201 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002202 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002203 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002204 f.write("[[[ %s ]]] %s\n" % (v, k))
2205 if not v and first_line is None:
2206 # position cursor on first line with no password.
2207 first_line = i + 4
2208 f.close()
2209
Tao Bao986ee862018-10-04 15:46:16 -07002210 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002211
2212 return self.ReadFile()
2213
2214 def ReadFile(self):
2215 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002216 if self.pwfile is None:
2217 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002218 try:
2219 f = open(self.pwfile, "r")
2220 for line in f:
2221 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002222 if not line or line[0] == '#':
2223 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002224 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2225 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002226 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002227 else:
2228 result[m.group(2)] = m.group(1)
2229 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002230 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002231 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002232 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002233 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002234
2235
Dan Albert8e0178d2015-01-27 15:53:15 -08002236def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2237 compress_type=None):
2238 import datetime
2239
2240 # http://b/18015246
2241 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2242 # for files larger than 2GiB. We can work around this by adjusting their
2243 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2244 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2245 # it isn't clear to me exactly what circumstances cause this).
2246 # `zipfile.write()` must be used directly to work around this.
2247 #
2248 # This mess can be avoided if we port to python3.
2249 saved_zip64_limit = zipfile.ZIP64_LIMIT
2250 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2251
2252 if compress_type is None:
2253 compress_type = zip_file.compression
2254 if arcname is None:
2255 arcname = filename
2256
2257 saved_stat = os.stat(filename)
2258
2259 try:
2260 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2261 # file to be zipped and reset it when we're done.
2262 os.chmod(filename, perms)
2263
2264 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002265 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2266 # intentional. zip stores datetimes in local time without a time zone
2267 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2268 # in the zip archive.
2269 local_epoch = datetime.datetime.fromtimestamp(0)
2270 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002271 os.utime(filename, (timestamp, timestamp))
2272
2273 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2274 finally:
2275 os.chmod(filename, saved_stat.st_mode)
2276 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2277 zipfile.ZIP64_LIMIT = saved_zip64_limit
2278
2279
Tao Bao58c1b962015-05-20 09:32:18 -07002280def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002281 compress_type=None):
2282 """Wrap zipfile.writestr() function to work around the zip64 limit.
2283
2284 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2285 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2286 when calling crc32(bytes).
2287
2288 But it still works fine to write a shorter string into a large zip file.
2289 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2290 when we know the string won't be too long.
2291 """
2292
2293 saved_zip64_limit = zipfile.ZIP64_LIMIT
2294 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2295
2296 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2297 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002298 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002299 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002300 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002301 else:
Tao Baof3282b42015-04-01 11:21:55 -07002302 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002303 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2304 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2305 # such a case (since
2306 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2307 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2308 # permission bits. We follow the logic in Python 3 to get consistent
2309 # behavior between using the two versions.
2310 if not zinfo.external_attr:
2311 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002312
2313 # If compress_type is given, it overrides the value in zinfo.
2314 if compress_type is not None:
2315 zinfo.compress_type = compress_type
2316
Tao Bao58c1b962015-05-20 09:32:18 -07002317 # If perms is given, it has a priority.
2318 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002319 # If perms doesn't set the file type, mark it as a regular file.
2320 if perms & 0o770000 == 0:
2321 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002322 zinfo.external_attr = perms << 16
2323
Tao Baof3282b42015-04-01 11:21:55 -07002324 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002325 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2326
Dan Albert8b72aef2015-03-23 19:13:21 -07002327 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002328 zipfile.ZIP64_LIMIT = saved_zip64_limit
2329
2330
Tao Bao89d7ab22017-12-14 17:05:33 -08002331def ZipDelete(zip_filename, entries):
2332 """Deletes entries from a ZIP file.
2333
2334 Since deleting entries from a ZIP file is not supported, it shells out to
2335 'zip -d'.
2336
2337 Args:
2338 zip_filename: The name of the ZIP file.
2339 entries: The name of the entry, or the list of names to be deleted.
2340
2341 Raises:
2342 AssertionError: In case of non-zero return from 'zip'.
2343 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002344 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002345 entries = [entries]
2346 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002347 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002348
2349
Tao Baof3282b42015-04-01 11:21:55 -07002350def ZipClose(zip_file):
2351 # http://b/18015246
2352 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2353 # central directory.
2354 saved_zip64_limit = zipfile.ZIP64_LIMIT
2355 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2356
2357 zip_file.close()
2358
2359 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002360
2361
2362class DeviceSpecificParams(object):
2363 module = None
2364 def __init__(self, **kwargs):
2365 """Keyword arguments to the constructor become attributes of this
2366 object, which is passed to all functions in the device-specific
2367 module."""
Tao Bao38884282019-07-10 22:20:56 -07002368 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002369 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002370 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002371
2372 if self.module is None:
2373 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002374 if not path:
2375 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002376 try:
2377 if os.path.isdir(path):
2378 info = imp.find_module("releasetools", [path])
2379 else:
2380 d, f = os.path.split(path)
2381 b, x = os.path.splitext(f)
2382 if x == ".py":
2383 f = b
2384 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002385 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002386 self.module = imp.load_module("device_specific", *info)
2387 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002388 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002389
2390 def _DoCall(self, function_name, *args, **kwargs):
2391 """Call the named function in the device-specific module, passing
2392 the given args and kwargs. The first argument to the call will be
2393 the DeviceSpecific object itself. If there is no module, or the
2394 module does not define the function, return the value of the
2395 'default' kwarg (which itself defaults to None)."""
2396 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002397 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002398 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2399
2400 def FullOTA_Assertions(self):
2401 """Called after emitting the block of assertions at the top of a
2402 full OTA package. Implementations can add whatever additional
2403 assertions they like."""
2404 return self._DoCall("FullOTA_Assertions")
2405
Doug Zongkere5ff5902012-01-17 10:55:37 -08002406 def FullOTA_InstallBegin(self):
2407 """Called at the start of full OTA installation."""
2408 return self._DoCall("FullOTA_InstallBegin")
2409
Yifan Hong10c530d2018-12-27 17:34:18 -08002410 def FullOTA_GetBlockDifferences(self):
2411 """Called during full OTA installation and verification.
2412 Implementation should return a list of BlockDifference objects describing
2413 the update on each additional partitions.
2414 """
2415 return self._DoCall("FullOTA_GetBlockDifferences")
2416
Doug Zongker05d3dea2009-06-22 11:32:31 -07002417 def FullOTA_InstallEnd(self):
2418 """Called at the end of full OTA installation; typically this is
2419 used to install the image for the device's baseband processor."""
2420 return self._DoCall("FullOTA_InstallEnd")
2421
2422 def IncrementalOTA_Assertions(self):
2423 """Called after emitting the block of assertions at the top of an
2424 incremental OTA package. Implementations can add whatever
2425 additional assertions they like."""
2426 return self._DoCall("IncrementalOTA_Assertions")
2427
Doug Zongkere5ff5902012-01-17 10:55:37 -08002428 def IncrementalOTA_VerifyBegin(self):
2429 """Called at the start of the verification phase of incremental
2430 OTA installation; additional checks can be placed here to abort
2431 the script before any changes are made."""
2432 return self._DoCall("IncrementalOTA_VerifyBegin")
2433
Doug Zongker05d3dea2009-06-22 11:32:31 -07002434 def IncrementalOTA_VerifyEnd(self):
2435 """Called at the end of the verification phase of incremental OTA
2436 installation; additional checks can be placed here to abort the
2437 script before any changes are made."""
2438 return self._DoCall("IncrementalOTA_VerifyEnd")
2439
Doug Zongkere5ff5902012-01-17 10:55:37 -08002440 def IncrementalOTA_InstallBegin(self):
2441 """Called at the start of incremental OTA installation (after
2442 verification is complete)."""
2443 return self._DoCall("IncrementalOTA_InstallBegin")
2444
Yifan Hong10c530d2018-12-27 17:34:18 -08002445 def IncrementalOTA_GetBlockDifferences(self):
2446 """Called during incremental OTA installation and verification.
2447 Implementation should return a list of BlockDifference objects describing
2448 the update on each additional partitions.
2449 """
2450 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2451
Doug Zongker05d3dea2009-06-22 11:32:31 -07002452 def IncrementalOTA_InstallEnd(self):
2453 """Called at the end of incremental OTA installation; typically
2454 this is used to install the image for the device's baseband
2455 processor."""
2456 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002457
Tao Bao9bc6bb22015-11-09 16:58:28 -08002458 def VerifyOTA_Assertions(self):
2459 return self._DoCall("VerifyOTA_Assertions")
2460
Tao Bao76def242017-11-21 09:25:31 -08002461
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002462class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002463 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002464 self.name = name
2465 self.data = data
2466 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002467 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002468 self.sha1 = sha1(data).hexdigest()
2469
2470 @classmethod
2471 def FromLocalFile(cls, name, diskname):
2472 f = open(diskname, "rb")
2473 data = f.read()
2474 f.close()
2475 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002476
2477 def WriteToTemp(self):
2478 t = tempfile.NamedTemporaryFile()
2479 t.write(self.data)
2480 t.flush()
2481 return t
2482
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002483 def WriteToDir(self, d):
2484 with open(os.path.join(d, self.name), "wb") as fp:
2485 fp.write(self.data)
2486
Geremy Condra36bd3652014-02-06 19:45:10 -08002487 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002488 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002489
Tao Bao76def242017-11-21 09:25:31 -08002490
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002491DIFF_PROGRAM_BY_EXT = {
2492 ".gz" : "imgdiff",
2493 ".zip" : ["imgdiff", "-z"],
2494 ".jar" : ["imgdiff", "-z"],
2495 ".apk" : ["imgdiff", "-z"],
2496 ".img" : "imgdiff",
2497 }
2498
Tao Bao76def242017-11-21 09:25:31 -08002499
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002500class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002501 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002502 self.tf = tf
2503 self.sf = sf
2504 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002505 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002506
2507 def ComputePatch(self):
2508 """Compute the patch (as a string of data) needed to turn sf into
2509 tf. Returns the same tuple as GetPatch()."""
2510
2511 tf = self.tf
2512 sf = self.sf
2513
Doug Zongker24cd2802012-08-14 16:36:15 -07002514 if self.diff_program:
2515 diff_program = self.diff_program
2516 else:
2517 ext = os.path.splitext(tf.name)[1]
2518 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002519
2520 ttemp = tf.WriteToTemp()
2521 stemp = sf.WriteToTemp()
2522
2523 ext = os.path.splitext(tf.name)[1]
2524
2525 try:
2526 ptemp = tempfile.NamedTemporaryFile()
2527 if isinstance(diff_program, list):
2528 cmd = copy.copy(diff_program)
2529 else:
2530 cmd = [diff_program]
2531 cmd.append(stemp.name)
2532 cmd.append(ttemp.name)
2533 cmd.append(ptemp.name)
2534 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002535 err = []
2536 def run():
2537 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002538 if e:
2539 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002540 th = threading.Thread(target=run)
2541 th.start()
2542 th.join(timeout=300) # 5 mins
2543 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002544 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002545 p.terminate()
2546 th.join(5)
2547 if th.is_alive():
2548 p.kill()
2549 th.join()
2550
Tianjie Xua2a9f992018-01-05 15:15:54 -08002551 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002552 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002553 self.patch = None
2554 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002555 diff = ptemp.read()
2556 finally:
2557 ptemp.close()
2558 stemp.close()
2559 ttemp.close()
2560
2561 self.patch = diff
2562 return self.tf, self.sf, self.patch
2563
2564
2565 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002566 """Returns a tuple of (target_file, source_file, patch_data).
2567
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002568 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002569 computing the patch failed.
2570 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002571 return self.tf, self.sf, self.patch
2572
2573
2574def ComputeDifferences(diffs):
2575 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002576 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002577
2578 # Do the largest files first, to try and reduce the long-pole effect.
2579 by_size = [(i.tf.size, i) for i in diffs]
2580 by_size.sort(reverse=True)
2581 by_size = [i[1] for i in by_size]
2582
2583 lock = threading.Lock()
2584 diff_iter = iter(by_size) # accessed under lock
2585
2586 def worker():
2587 try:
2588 lock.acquire()
2589 for d in diff_iter:
2590 lock.release()
2591 start = time.time()
2592 d.ComputePatch()
2593 dur = time.time() - start
2594 lock.acquire()
2595
2596 tf, sf, patch = d.GetPatch()
2597 if sf.name == tf.name:
2598 name = tf.name
2599 else:
2600 name = "%s (%s)" % (tf.name, sf.name)
2601 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002602 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002603 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002604 logger.info(
2605 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2606 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002607 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002608 except Exception:
2609 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002610 raise
2611
2612 # start worker threads; wait for them all to finish.
2613 threads = [threading.Thread(target=worker)
2614 for i in range(OPTIONS.worker_threads)]
2615 for th in threads:
2616 th.start()
2617 while threads:
2618 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002619
2620
Dan Albert8b72aef2015-03-23 19:13:21 -07002621class BlockDifference(object):
2622 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002623 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002624 self.tgt = tgt
2625 self.src = src
2626 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002627 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002628 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002629
Tao Baodd2a5892015-03-12 12:32:37 -07002630 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002631 version = max(
2632 int(i) for i in
2633 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002634 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002635 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002636
Tianjie Xu41976c72019-07-03 13:57:01 -07002637 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2638 version=self.version,
2639 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002640 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002641 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002642 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002643 self.touched_src_ranges = b.touched_src_ranges
2644 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002645
Yifan Hong10c530d2018-12-27 17:34:18 -08002646 # On devices with dynamic partitions, for new partitions,
2647 # src is None but OPTIONS.source_info_dict is not.
2648 if OPTIONS.source_info_dict is None:
2649 is_dynamic_build = OPTIONS.info_dict.get(
2650 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002651 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002652 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002653 is_dynamic_build = OPTIONS.source_info_dict.get(
2654 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002655 is_dynamic_source = partition in shlex.split(
2656 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002657
Yifan Hongbb2658d2019-01-25 12:30:58 -08002658 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002659 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2660
Yifan Hongbb2658d2019-01-25 12:30:58 -08002661 # For dynamic partitions builds, check partition list in both source
2662 # and target build because new partitions may be added, and existing
2663 # partitions may be removed.
2664 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2665
Yifan Hong10c530d2018-12-27 17:34:18 -08002666 if is_dynamic:
2667 self.device = 'map_partition("%s")' % partition
2668 else:
2669 if OPTIONS.source_info_dict is None:
2670 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2671 else:
2672 _, device_path = GetTypeAndDevice("/" + partition,
2673 OPTIONS.source_info_dict)
2674 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002675
Tao Baod8d14be2016-02-04 14:26:02 -08002676 @property
2677 def required_cache(self):
2678 return self._required_cache
2679
Tao Bao76def242017-11-21 09:25:31 -08002680 def WriteScript(self, script, output_zip, progress=None,
2681 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002682 if not self.src:
2683 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002684 script.Print("Patching %s image unconditionally..." % (self.partition,))
2685 else:
2686 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002687
Dan Albert8b72aef2015-03-23 19:13:21 -07002688 if progress:
2689 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002690 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002691
2692 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002693 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002694
Tao Bao9bc6bb22015-11-09 16:58:28 -08002695 def WriteStrictVerifyScript(self, script):
2696 """Verify all the blocks in the care_map, including clobbered blocks.
2697
2698 This differs from the WriteVerifyScript() function: a) it prints different
2699 error messages; b) it doesn't allow half-way updated images to pass the
2700 verification."""
2701
2702 partition = self.partition
2703 script.Print("Verifying %s..." % (partition,))
2704 ranges = self.tgt.care_map
2705 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002706 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002707 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2708 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002709 self.device, ranges_str,
2710 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002711 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002712 script.AppendExtra("")
2713
Tao Baod522bdc2016-04-12 15:53:16 -07002714 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002715 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002716
2717 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002718 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002719 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002720
2721 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002722 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002723 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002724 ranges = self.touched_src_ranges
2725 expected_sha1 = self.touched_src_sha1
2726 else:
2727 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2728 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002729
2730 # No blocks to be checked, skipping.
2731 if not ranges:
2732 return
2733
Tao Bao5ece99d2015-05-12 11:42:31 -07002734 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002735 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002736 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002737 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2738 '"%s.patch.dat")) then' % (
2739 self.device, ranges_str, expected_sha1,
2740 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002741 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002742 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002743
Tianjie Xufc3422a2015-12-15 11:53:59 -08002744 if self.version >= 4:
2745
2746 # Bug: 21124327
2747 # When generating incrementals for the system and vendor partitions in
2748 # version 4 or newer, explicitly check the first block (which contains
2749 # the superblock) of the partition to see if it's what we expect. If
2750 # this check fails, give an explicit log message about the partition
2751 # having been remounted R/W (the most likely explanation).
2752 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002753 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002754
2755 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002756 if partition == "system":
2757 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2758 else:
2759 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002760 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002761 'ifelse (block_image_recover({device}, "{ranges}") && '
2762 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002763 'package_extract_file("{partition}.transfer.list"), '
2764 '"{partition}.new.dat", "{partition}.patch.dat"), '
2765 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002766 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002767 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002768 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002769
Tao Baodd2a5892015-03-12 12:32:37 -07002770 # Abort the OTA update. Note that the incremental OTA cannot be applied
2771 # even if it may match the checksum of the target partition.
2772 # a) If version < 3, operations like move and erase will make changes
2773 # unconditionally and damage the partition.
2774 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002775 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002776 if partition == "system":
2777 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2778 else:
2779 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2780 script.AppendExtra((
2781 'abort("E%d: %s partition has unexpected contents");\n'
2782 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002783
Yifan Hong10c530d2018-12-27 17:34:18 -08002784 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002785 partition = self.partition
2786 script.Print('Verifying the updated %s image...' % (partition,))
2787 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2788 ranges = self.tgt.care_map
2789 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002790 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002791 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002792 self.device, ranges_str,
2793 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002794
2795 # Bug: 20881595
2796 # Verify that extended blocks are really zeroed out.
2797 if self.tgt.extended:
2798 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002799 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002800 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002801 self.device, ranges_str,
2802 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002803 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002804 if partition == "system":
2805 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2806 else:
2807 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002808 script.AppendExtra(
2809 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002810 ' abort("E%d: %s partition has unexpected non-zero contents after '
2811 'OTA update");\n'
2812 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002813 else:
2814 script.Print('Verified the updated %s image.' % (partition,))
2815
Tianjie Xu209db462016-05-24 17:34:52 -07002816 if partition == "system":
2817 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2818 else:
2819 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2820
Tao Bao5fcaaef2015-06-01 13:40:49 -07002821 script.AppendExtra(
2822 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002823 ' abort("E%d: %s partition has unexpected contents after OTA '
2824 'update");\n'
2825 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002826
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002827 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002828 ZipWrite(output_zip,
2829 '{}.transfer.list'.format(self.path),
2830 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002831
Tao Bao76def242017-11-21 09:25:31 -08002832 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2833 # its size. Quailty 9 almost triples the compression time but doesn't
2834 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002835 # zip | brotli(quality 6) | brotli(quality 9)
2836 # compressed_size: 942M | 869M (~8% reduced) | 854M
2837 # compression_time: 75s | 265s | 719s
2838 # decompression_time: 15s | 25s | 25s
2839
2840 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002841 brotli_cmd = ['brotli', '--quality=6',
2842 '--output={}.new.dat.br'.format(self.path),
2843 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002844 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002845 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002846
2847 new_data_name = '{}.new.dat.br'.format(self.partition)
2848 ZipWrite(output_zip,
2849 '{}.new.dat.br'.format(self.path),
2850 new_data_name,
2851 compress_type=zipfile.ZIP_STORED)
2852 else:
2853 new_data_name = '{}.new.dat'.format(self.partition)
2854 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2855
Dan Albert8e0178d2015-01-27 15:53:15 -08002856 ZipWrite(output_zip,
2857 '{}.patch.dat'.format(self.path),
2858 '{}.patch.dat'.format(self.partition),
2859 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002860
Tianjie Xu209db462016-05-24 17:34:52 -07002861 if self.partition == "system":
2862 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2863 else:
2864 code = ErrorCode.VENDOR_UPDATE_FAILURE
2865
Yifan Hong10c530d2018-12-27 17:34:18 -08002866 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002867 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002868 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002869 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002870 device=self.device, partition=self.partition,
2871 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002872 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002873
Dan Albert8b72aef2015-03-23 19:13:21 -07002874 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002875 data = source.ReadRangeSet(ranges)
2876 ctx = sha1()
2877
2878 for p in data:
2879 ctx.update(p)
2880
2881 return ctx.hexdigest()
2882
Tao Baoe9b61912015-07-09 17:37:49 -07002883 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2884 """Return the hash value for all zero blocks."""
2885 zero_block = '\x00' * 4096
2886 ctx = sha1()
2887 for _ in range(num_blocks):
2888 ctx.update(zero_block)
2889
2890 return ctx.hexdigest()
2891
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002892
Tianjie Xu41976c72019-07-03 13:57:01 -07002893# Expose these two classes to support vendor-specific scripts
2894DataImage = images.DataImage
2895EmptyImage = images.EmptyImage
2896
Tao Bao76def242017-11-21 09:25:31 -08002897
Doug Zongker96a57e72010-09-26 14:57:41 -07002898# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002899PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002900 "ext4": "EMMC",
2901 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002902 "f2fs": "EMMC",
2903 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002904}
Doug Zongker96a57e72010-09-26 14:57:41 -07002905
Tao Bao76def242017-11-21 09:25:31 -08002906
Doug Zongker96a57e72010-09-26 14:57:41 -07002907def GetTypeAndDevice(mount_point, info):
2908 fstab = info["fstab"]
2909 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002910 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2911 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002912 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002913 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002914
2915
2916def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002917 """Parses and converts a PEM-encoded certificate into DER-encoded.
2918
2919 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2920
2921 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002922 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002923 """
2924 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002925 save = False
2926 for line in data.split("\n"):
2927 if "--END CERTIFICATE--" in line:
2928 break
2929 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002930 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002931 if "--BEGIN CERTIFICATE--" in line:
2932 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002933 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002934 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002935
Tao Bao04e1f012018-02-04 12:13:35 -08002936
2937def ExtractPublicKey(cert):
2938 """Extracts the public key (PEM-encoded) from the given certificate file.
2939
2940 Args:
2941 cert: The certificate filename.
2942
2943 Returns:
2944 The public key string.
2945
2946 Raises:
2947 AssertionError: On non-zero return from 'openssl'.
2948 """
2949 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2950 # While openssl 1.1 writes the key into the given filename followed by '-out',
2951 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2952 # stdout instead.
2953 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2954 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2955 pubkey, stderrdata = proc.communicate()
2956 assert proc.returncode == 0, \
2957 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2958 return pubkey
2959
2960
Tao Bao1ac886e2019-06-26 11:58:22 -07002961def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002962 """Extracts the AVB public key from the given public or private key.
2963
2964 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002965 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002966 key: The input key file, which should be PEM-encoded public or private key.
2967
2968 Returns:
2969 The path to the extracted AVB public key file.
2970 """
2971 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2972 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002973 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002974 return output
2975
2976
Doug Zongker412c02f2014-02-13 10:58:24 -08002977def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2978 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002979 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002980
Tao Bao6d5d6232018-03-09 17:04:42 -08002981 Most of the space in the boot and recovery images is just the kernel, which is
2982 identical for the two, so the resulting patch should be efficient. Add it to
2983 the output zip, along with a shell script that is run from init.rc on first
2984 boot to actually do the patching and install the new recovery image.
2985
2986 Args:
2987 input_dir: The top-level input directory of the target-files.zip.
2988 output_sink: The callback function that writes the result.
2989 recovery_img: File object for the recovery image.
2990 boot_img: File objects for the boot image.
2991 info_dict: A dict returned by common.LoadInfoDict() on the input
2992 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002993 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002994 if info_dict is None:
2995 info_dict = OPTIONS.info_dict
2996
Tao Bao6d5d6232018-03-09 17:04:42 -08002997 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002998 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2999
3000 if board_uses_vendorimage:
3001 # In this case, the output sink is rooted at VENDOR
3002 recovery_img_path = "etc/recovery.img"
3003 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3004 sh_dir = "bin"
3005 else:
3006 # In this case the output sink is rooted at SYSTEM
3007 recovery_img_path = "vendor/etc/recovery.img"
3008 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3009 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003010
Tao Baof2cffbd2015-07-22 12:33:18 -07003011 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003012 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003013
3014 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003015 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003016 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003017 # With system-root-image, boot and recovery images will have mismatching
3018 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3019 # to handle such a case.
3020 if system_root_image:
3021 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003022 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003023 assert not os.path.exists(path)
3024 else:
3025 diff_program = ["imgdiff"]
3026 if os.path.exists(path):
3027 diff_program.append("-b")
3028 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003029 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003030 else:
3031 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003032
3033 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3034 _, _, patch = d.ComputePatch()
3035 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003036
Dan Albertebb19aa2015-03-27 19:11:53 -07003037 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003038 # The following GetTypeAndDevice()s need to use the path in the target
3039 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07003040 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
3041 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
3042 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003043 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003044
Tao Baof2cffbd2015-07-22 12:33:18 -07003045 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003046
3047 # Note that we use /vendor to refer to the recovery resources. This will
3048 # work for a separate vendor partition mounted at /vendor or a
3049 # /system/vendor subdirectory on the system partition, for which init will
3050 # create a symlink from /vendor to /system/vendor.
3051
3052 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003053if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3054 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003055 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003056 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3057 log -t recovery "Installing new recovery image: succeeded" || \\
3058 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003059else
3060 log -t recovery "Recovery image already installed"
3061fi
3062""" % {'type': recovery_type,
3063 'device': recovery_device,
3064 'sha1': recovery_img.sha1,
3065 'size': recovery_img.size}
3066 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003067 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003068if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3069 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003070 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003071 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3072 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3073 log -t recovery "Installing new recovery image: succeeded" || \\
3074 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003075else
3076 log -t recovery "Recovery image already installed"
3077fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003078""" % {'boot_size': boot_img.size,
3079 'boot_sha1': boot_img.sha1,
3080 'recovery_size': recovery_img.size,
3081 'recovery_sha1': recovery_img.sha1,
3082 'boot_type': boot_type,
3083 'boot_device': boot_device,
3084 'recovery_type': recovery_type,
3085 'recovery_device': recovery_device,
3086 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003087
Bill Peckhame868aec2019-09-17 17:06:47 -07003088 # The install script location moved from /system/etc to /system/bin in the L
3089 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3090 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003091
Tao Bao32fcdab2018-10-12 10:30:39 -07003092 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003093
Tao Baoda30cfa2017-12-01 16:19:46 -08003094 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003095
3096
3097class DynamicPartitionUpdate(object):
3098 def __init__(self, src_group=None, tgt_group=None, progress=None,
3099 block_difference=None):
3100 self.src_group = src_group
3101 self.tgt_group = tgt_group
3102 self.progress = progress
3103 self.block_difference = block_difference
3104
3105 @property
3106 def src_size(self):
3107 if not self.block_difference:
3108 return 0
3109 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3110
3111 @property
3112 def tgt_size(self):
3113 if not self.block_difference:
3114 return 0
3115 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3116
3117 @staticmethod
3118 def _GetSparseImageSize(img):
3119 if not img:
3120 return 0
3121 return img.blocksize * img.total_blocks
3122
3123
3124class DynamicGroupUpdate(object):
3125 def __init__(self, src_size=None, tgt_size=None):
3126 # None: group does not exist. 0: no size limits.
3127 self.src_size = src_size
3128 self.tgt_size = tgt_size
3129
3130
3131class DynamicPartitionsDifference(object):
3132 def __init__(self, info_dict, block_diffs, progress_dict=None,
3133 source_info_dict=None):
3134 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003135 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003136
3137 self._remove_all_before_apply = False
3138 if source_info_dict is None:
3139 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003140 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003141
Tao Baof1113e92019-06-18 12:10:14 -07003142 block_diff_dict = collections.OrderedDict(
3143 [(e.partition, e) for e in block_diffs])
3144
Yifan Hong10c530d2018-12-27 17:34:18 -08003145 assert len(block_diff_dict) == len(block_diffs), \
3146 "Duplicated BlockDifference object for {}".format(
3147 [partition for partition, count in
3148 collections.Counter(e.partition for e in block_diffs).items()
3149 if count > 1])
3150
Yifan Hong79997e52019-01-23 16:56:19 -08003151 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003152
3153 for p, block_diff in block_diff_dict.items():
3154 self._partition_updates[p] = DynamicPartitionUpdate()
3155 self._partition_updates[p].block_difference = block_diff
3156
3157 for p, progress in progress_dict.items():
3158 if p in self._partition_updates:
3159 self._partition_updates[p].progress = progress
3160
3161 tgt_groups = shlex.split(info_dict.get(
3162 "super_partition_groups", "").strip())
3163 src_groups = shlex.split(source_info_dict.get(
3164 "super_partition_groups", "").strip())
3165
3166 for g in tgt_groups:
3167 for p in shlex.split(info_dict.get(
3168 "super_%s_partition_list" % g, "").strip()):
3169 assert p in self._partition_updates, \
3170 "{} is in target super_{}_partition_list but no BlockDifference " \
3171 "object is provided.".format(p, g)
3172 self._partition_updates[p].tgt_group = g
3173
3174 for g in src_groups:
3175 for p in shlex.split(source_info_dict.get(
3176 "super_%s_partition_list" % g, "").strip()):
3177 assert p in self._partition_updates, \
3178 "{} is in source super_{}_partition_list but no BlockDifference " \
3179 "object is provided.".format(p, g)
3180 self._partition_updates[p].src_group = g
3181
Yifan Hong45433e42019-01-18 13:55:25 -08003182 target_dynamic_partitions = set(shlex.split(info_dict.get(
3183 "dynamic_partition_list", "").strip()))
3184 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3185 if u.tgt_size)
3186 assert block_diffs_with_target == target_dynamic_partitions, \
3187 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3188 list(target_dynamic_partitions), list(block_diffs_with_target))
3189
3190 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3191 "dynamic_partition_list", "").strip()))
3192 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3193 if u.src_size)
3194 assert block_diffs_with_source == source_dynamic_partitions, \
3195 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3196 list(source_dynamic_partitions), list(block_diffs_with_source))
3197
Yifan Hong10c530d2018-12-27 17:34:18 -08003198 if self._partition_updates:
3199 logger.info("Updating dynamic partitions %s",
3200 self._partition_updates.keys())
3201
Yifan Hong79997e52019-01-23 16:56:19 -08003202 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003203
3204 for g in tgt_groups:
3205 self._group_updates[g] = DynamicGroupUpdate()
3206 self._group_updates[g].tgt_size = int(info_dict.get(
3207 "super_%s_group_size" % g, "0").strip())
3208
3209 for g in src_groups:
3210 if g not in self._group_updates:
3211 self._group_updates[g] = DynamicGroupUpdate()
3212 self._group_updates[g].src_size = int(source_info_dict.get(
3213 "super_%s_group_size" % g, "0").strip())
3214
3215 self._Compute()
3216
3217 def WriteScript(self, script, output_zip, write_verify_script=False):
3218 script.Comment('--- Start patching dynamic partitions ---')
3219 for p, u in self._partition_updates.items():
3220 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3221 script.Comment('Patch partition %s' % p)
3222 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3223 write_verify_script=False)
3224
3225 op_list_path = MakeTempFile()
3226 with open(op_list_path, 'w') as f:
3227 for line in self._op_list:
3228 f.write('{}\n'.format(line))
3229
3230 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3231
3232 script.Comment('Update dynamic partition metadata')
3233 script.AppendExtra('assert(update_dynamic_partitions('
3234 'package_extract_file("dynamic_partitions_op_list")));')
3235
3236 if write_verify_script:
3237 for p, u in self._partition_updates.items():
3238 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3239 u.block_difference.WritePostInstallVerifyScript(script)
3240 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3241
3242 for p, u in self._partition_updates.items():
3243 if u.tgt_size and u.src_size <= u.tgt_size:
3244 script.Comment('Patch partition %s' % p)
3245 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3246 write_verify_script=write_verify_script)
3247 if write_verify_script:
3248 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3249
3250 script.Comment('--- End patching dynamic partitions ---')
3251
3252 def _Compute(self):
3253 self._op_list = list()
3254
3255 def append(line):
3256 self._op_list.append(line)
3257
3258 def comment(line):
3259 self._op_list.append("# %s" % line)
3260
3261 if self._remove_all_before_apply:
3262 comment('Remove all existing dynamic partitions and groups before '
3263 'applying full OTA')
3264 append('remove_all_groups')
3265
3266 for p, u in self._partition_updates.items():
3267 if u.src_group and not u.tgt_group:
3268 append('remove %s' % p)
3269
3270 for p, u in self._partition_updates.items():
3271 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3272 comment('Move partition %s from %s to default' % (p, u.src_group))
3273 append('move %s default' % p)
3274
3275 for p, u in self._partition_updates.items():
3276 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3277 comment('Shrink partition %s from %d to %d' %
3278 (p, u.src_size, u.tgt_size))
3279 append('resize %s %s' % (p, u.tgt_size))
3280
3281 for g, u in self._group_updates.items():
3282 if u.src_size is not None and u.tgt_size is None:
3283 append('remove_group %s' % g)
3284 if (u.src_size is not None and u.tgt_size is not None and
3285 u.src_size > u.tgt_size):
3286 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3287 append('resize_group %s %d' % (g, u.tgt_size))
3288
3289 for g, u in self._group_updates.items():
3290 if u.src_size is None and u.tgt_size is not None:
3291 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3292 append('add_group %s %d' % (g, u.tgt_size))
3293 if (u.src_size is not None and u.tgt_size is not None and
3294 u.src_size < u.tgt_size):
3295 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3296 append('resize_group %s %d' % (g, u.tgt_size))
3297
3298 for p, u in self._partition_updates.items():
3299 if u.tgt_group and not u.src_group:
3300 comment('Add partition %s to group %s' % (p, u.tgt_group))
3301 append('add %s %s' % (p, u.tgt_group))
3302
3303 for p, u in self._partition_updates.items():
3304 if u.tgt_size and u.src_size < u.tgt_size:
3305 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3306 append('resize %s %d' % (p, u.tgt_size))
3307
3308 for p, u in self._partition_updates.items():
3309 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3310 comment('Move partition %s from default to %s' %
3311 (p, u.tgt_group))
3312 append('move %s %s' % (p, u.tgt_group))