blob: 03e94f3dd09e137ec4760de617b1f9107aab635e [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")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800680 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700681
Steve Muckle903a1ca2020-05-07 17:32:10 -0700682 boot_images = "boot.img"
683 if "boot_images" in d:
684 boot_images = d["boot_images"]
685 for b in boot_images.split():
686 makeint(b.replace(".img","_size"))
687
Tao Bao765668f2019-10-04 22:03:00 -0700688 # Load recovery fstab if applicable.
689 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800690
Tianjie Xu861f4132018-09-12 11:49:33 -0700691 # Tries to load the build props for all partitions with care_map, including
692 # system and vendor.
693 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800694 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000695 d[partition_prop] = PartitionBuildProps.FromInputFile(
696 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700697 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800698
Tao Bao3ed35d32019-10-07 20:48:48 -0700699 # Set up the salt (based on fingerprint) that will be used when adding AVB
700 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800701 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700702 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800703 for partition in PARTITIONS_WITH_CARE_MAP:
704 fingerprint = build_info.GetPartitionFingerprint(partition)
705 if fingerprint:
706 d["avb_{}_salt".format(partition)] = sha256(fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800707
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700708 return d
709
Tao Baod1de6f32017-03-01 16:38:48 -0800710
Daniel Norman4cc9df62019-07-18 10:11:07 -0700711def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900712 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700713 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900714
Daniel Norman4cc9df62019-07-18 10:11:07 -0700715
716def LoadDictionaryFromFile(file_path):
717 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900718 return LoadDictionaryFromLines(lines)
719
720
Michael Runge6e836112014-04-15 17:40:21 -0700721def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700722 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700723 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700724 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700725 if not line or line.startswith("#"):
726 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700727 if "=" in line:
728 name, value = line.split("=", 1)
729 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700730 return d
731
Tao Baod1de6f32017-03-01 16:38:48 -0800732
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000733class PartitionBuildProps(object):
734 """The class holds the build prop of a particular partition.
735
736 This class loads the build.prop and holds the build properties for a given
737 partition. It also partially recognizes the 'import' statement in the
738 build.prop; and calculates alternative values of some specific build
739 properties during runtime.
740
741 Attributes:
742 input_file: a zipped target-file or an unzipped target-file directory.
743 partition: name of the partition.
744 props_allow_override: a list of build properties to search for the
745 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000746 build_props: a dict of build properties for the given partition.
747 prop_overrides: a set of props that are overridden by import.
748 placeholder_values: A dict of runtime variables' values to replace the
749 placeholders in the build.prop file. We expect exactly one value for
750 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000751 """
Tianjie Xu9afb2212020-05-10 21:48:15 +0000752 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000753 self.input_file = input_file
754 self.partition = name
755 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000756 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000757 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000758 self.prop_overrides = set()
759 self.placeholder_values = {}
760 if placeholder_values:
761 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000762
763 @staticmethod
764 def FromDictionary(name, build_props):
765 """Constructs an instance from a build prop dictionary."""
766
767 props = PartitionBuildProps("unknown", name)
768 props.build_props = build_props.copy()
769 return props
770
771 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000772 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000773 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000774 data = ''
775 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
776 '{}/build.prop'.format(name.upper())]:
777 try:
778 data = ReadFromInputFile(input_file, prop_file)
779 break
780 except KeyError:
781 logger.warning('Failed to read %s', prop_file)
782
Tianjie Xu9afb2212020-05-10 21:48:15 +0000783 props = PartitionBuildProps(input_file, name, placeholder_values)
784 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000785 return props
786
Tianjie Xu9afb2212020-05-10 21:48:15 +0000787 def _LoadBuildProp(self, data):
788 for line in data.split('\n'):
789 line = line.strip()
790 if not line or line.startswith("#"):
791 continue
792 if line.startswith("import"):
793 overrides = self._ImportParser(line)
794 duplicates = self.prop_overrides.intersection(overrides.keys())
795 if duplicates:
796 raise ValueError('prop {} is overridden multiple times'.format(
797 ','.join(duplicates)))
798 self.prop_overrides = self.prop_overrides.union(overrides.keys())
799 self.build_props.update(overrides)
800 elif "=" in line:
801 name, value = line.split("=", 1)
802 if name in self.prop_overrides:
803 raise ValueError('prop {} is set again after overridden by import '
804 'statement'.format(name))
805 self.build_props[name] = value
806
807 def _ImportParser(self, line):
808 """Parses the build prop in a given import statement."""
809
810 tokens = line.split()
811 if len(tokens) != 2 or tokens[0] != 'import':
812 raise ValueError('Unrecognized import statement {}'.format(line))
813 import_path = tokens[1]
814 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
815 raise ValueError('Unrecognized import path {}'.format(line))
816
817 # We only recognize a subset of import statement that the init process
818 # supports. And we can loose the restriction based on how the dynamic
819 # fingerprint is used in practice. The placeholder format should be
820 # ${placeholder}, and its value should be provided by the caller through
821 # the placeholder_values.
822 for prop, value in self.placeholder_values.items():
823 prop_place_holder = '${{{}}}'.format(prop)
824 if prop_place_holder in import_path:
825 import_path = import_path.replace(prop_place_holder, value)
826 if '$' in import_path:
827 logger.info('Unresolved place holder in import path %s', import_path)
828 return {}
829
830 import_path = import_path.replace('/{}'.format(self.partition),
831 self.partition.upper())
832 logger.info('Parsing build props override from %s', import_path)
833
834 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
835 d = LoadDictionaryFromLines(lines)
836 return {key: val for key, val in d.items()
837 if key in self.props_allow_override}
838
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000839 def GetProp(self, prop):
840 return self.build_props.get(prop)
841
842
Tianjie Xucfa86222016-03-07 16:31:19 -0800843def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
844 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700845 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800846 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700847 self.mount_point = mount_point
848 self.fs_type = fs_type
849 self.device = device
850 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700851 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700852
853 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800854 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700855 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700856 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700857 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700858
Tao Baod1de6f32017-03-01 16:38:48 -0800859 assert fstab_version == 2
860
861 d = {}
862 for line in data.split("\n"):
863 line = line.strip()
864 if not line or line.startswith("#"):
865 continue
866
867 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
868 pieces = line.split()
869 if len(pieces) != 5:
870 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
871
872 # Ignore entries that are managed by vold.
873 options = pieces[4]
874 if "voldmanaged=" in options:
875 continue
876
877 # It's a good line, parse it.
878 length = 0
879 options = options.split(",")
880 for i in options:
881 if i.startswith("length="):
882 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800883 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800884 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700885 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800886
Tao Baod1de6f32017-03-01 16:38:48 -0800887 mount_flags = pieces[3]
888 # Honor the SELinux context if present.
889 context = None
890 for i in mount_flags.split(","):
891 if i.startswith("context="):
892 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800893
Tao Baod1de6f32017-03-01 16:38:48 -0800894 mount_point = pieces[1]
895 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
896 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800897
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700898 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700899 # system. Other areas assume system is always at "/system" so point /system
900 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700901 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800902 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700903 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700904 return d
905
906
Tao Bao765668f2019-10-04 22:03:00 -0700907def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
908 """Finds the path to recovery fstab and loads its contents."""
909 # recovery fstab is only meaningful when installing an update via recovery
910 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
911 if info_dict.get('ab_update') == 'true':
912 return None
913
914 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
915 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
916 # cases, since it may load the info_dict from an old build (e.g. when
917 # generating incremental OTAs from that build).
918 system_root_image = info_dict.get('system_root_image') == 'true'
919 if info_dict.get('no_recovery') != 'true':
920 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
921 if isinstance(input_file, zipfile.ZipFile):
922 if recovery_fstab_path not in input_file.namelist():
923 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
924 else:
925 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
926 if not os.path.exists(path):
927 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
928 return LoadRecoveryFSTab(
929 read_helper, info_dict['fstab_version'], recovery_fstab_path,
930 system_root_image)
931
932 if info_dict.get('recovery_as_boot') == 'true':
933 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
934 if isinstance(input_file, zipfile.ZipFile):
935 if recovery_fstab_path not in input_file.namelist():
936 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
937 else:
938 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
939 if not os.path.exists(path):
940 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
941 return LoadRecoveryFSTab(
942 read_helper, info_dict['fstab_version'], recovery_fstab_path,
943 system_root_image)
944
945 return None
946
947
Doug Zongker37974732010-09-16 17:44:38 -0700948def DumpInfoDict(d):
949 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700950 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700951
Dan Albert8b72aef2015-03-23 19:13:21 -0700952
Daniel Norman55417142019-11-25 16:04:36 -0800953def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700954 """Merges dynamic partition info variables.
955
956 Args:
957 framework_dict: The dictionary of dynamic partition info variables from the
958 partial framework target files.
959 vendor_dict: The dictionary of dynamic partition info variables from the
960 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700961
962 Returns:
963 The merged dynamic partition info dictionary.
964 """
965 merged_dict = {}
966 # Partition groups and group sizes are defined by the vendor dict because
967 # these values may vary for each board that uses a shared system image.
968 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800969 framework_dynamic_partition_list = framework_dict.get(
970 "dynamic_partition_list", "")
971 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
972 merged_dict["dynamic_partition_list"] = ("%s %s" % (
973 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700974 for partition_group in merged_dict["super_partition_groups"].split(" "):
975 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800976 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700977 if key not in vendor_dict:
978 raise ValueError("Vendor dict does not contain required key %s." % key)
979 merged_dict[key] = vendor_dict[key]
980
981 # Set the partition group's partition list using a concatenation of the
982 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800983 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700984 merged_dict[key] = (
985 "%s %s" %
986 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530987
988 # Pick virtual ab related flags from vendor dict, if defined.
989 if "virtual_ab" in vendor_dict.keys():
990 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
991 if "virtual_ab_retrofit" in vendor_dict.keys():
992 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700993 return merged_dict
994
995
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800996def AppendAVBSigningArgs(cmd, partition):
997 """Append signing arguments for avbtool."""
998 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
999 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001000 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1001 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1002 if os.path.exists(new_key_path):
1003 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001004 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1005 if key_path and algorithm:
1006 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001007 avb_salt = OPTIONS.info_dict.get("avb_salt")
1008 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001009 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001010 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001011
1012
Tao Bao765668f2019-10-04 22:03:00 -07001013def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001014 """Returns the VBMeta arguments for partition.
1015
1016 It sets up the VBMeta argument by including the partition descriptor from the
1017 given 'image', or by configuring the partition as a chained partition.
1018
1019 Args:
1020 partition: The name of the partition (e.g. "system").
1021 image: The path to the partition image.
1022 info_dict: A dict returned by common.LoadInfoDict(). Will use
1023 OPTIONS.info_dict if None has been given.
1024
1025 Returns:
1026 A list of VBMeta arguments.
1027 """
1028 if info_dict is None:
1029 info_dict = OPTIONS.info_dict
1030
1031 # Check if chain partition is used.
1032 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001033 if not key_path:
1034 return ["--include_descriptors_from_image", image]
1035
1036 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1037 # into vbmeta.img. The recovery image will be configured on an independent
1038 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1039 # See details at
1040 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001041 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001042 return []
1043
1044 # Otherwise chain the partition into vbmeta.
1045 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1046 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001047
1048
Tao Bao02a08592018-07-22 12:40:45 -07001049def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1050 """Constructs and returns the arg to build or verify a chained partition.
1051
1052 Args:
1053 partition: The partition name.
1054 info_dict: The info dict to look up the key info and rollback index
1055 location.
1056 key: The key to be used for building or verifying the partition. Defaults to
1057 the key listed in info_dict.
1058
1059 Returns:
1060 A string of form "partition:rollback_index_location:key" that can be used to
1061 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001062 """
1063 if key is None:
1064 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001065 if key and not os.path.exists(key) and OPTIONS.search_path:
1066 new_key_path = os.path.join(OPTIONS.search_path, key)
1067 if os.path.exists(new_key_path):
1068 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001069 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001070 rollback_index_location = info_dict[
1071 "avb_" + partition + "_rollback_index_location"]
1072 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1073
1074
Tianjie20dd8f22020-04-19 15:51:16 -07001075def ConstructAftlMakeImageCommands(output_image):
1076 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001077
1078 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001079 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001080 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1081 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1082 'No AFTL manufacturer key provided.'
1083
1084 vbmeta_image = MakeTempFile()
1085 os.rename(output_image, vbmeta_image)
1086 build_info = BuildInfo(OPTIONS.info_dict)
1087 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001088 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001089 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001090 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001091 "--vbmeta_image_path", vbmeta_image,
1092 "--output", output_image,
1093 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001094 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001095 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1096 "--algorithm", "SHA256_RSA4096",
1097 "--padding", "4096"]
1098 if OPTIONS.aftl_signer_helper:
1099 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001100 return aftl_cmd
1101
1102
1103def AddAftlInclusionProof(output_image):
1104 """Appends the aftl inclusion proof to the vbmeta image."""
1105
1106 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001107 RunAndCheckOutput(aftl_cmd)
1108
1109 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1110 output_image, '--transparency_log_pub_keys',
1111 OPTIONS.aftl_key_path]
1112 RunAndCheckOutput(verify_cmd)
1113
1114
Daniel Norman276f0622019-07-26 14:13:51 -07001115def BuildVBMeta(image_path, partitions, name, needed_partitions):
1116 """Creates a VBMeta image.
1117
1118 It generates the requested VBMeta image. The requested image could be for
1119 top-level or chained VBMeta image, which is determined based on the name.
1120
1121 Args:
1122 image_path: The output path for the new VBMeta image.
1123 partitions: A dict that's keyed by partition names with image paths as
1124 values. Only valid partition names are accepted, as listed in
1125 common.AVB_PARTITIONS.
1126 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1127 needed_partitions: Partitions whose descriptors should be included into the
1128 generated VBMeta image.
1129
1130 Raises:
1131 AssertionError: On invalid input args.
1132 """
1133 avbtool = OPTIONS.info_dict["avb_avbtool"]
1134 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1135 AppendAVBSigningArgs(cmd, name)
1136
1137 for partition, path in partitions.items():
1138 if partition not in needed_partitions:
1139 continue
1140 assert (partition in AVB_PARTITIONS or
1141 partition in AVB_VBMETA_PARTITIONS), \
1142 'Unknown partition: {}'.format(partition)
1143 assert os.path.exists(path), \
1144 'Failed to find {} for {}'.format(path, partition)
1145 cmd.extend(GetAvbPartitionArg(partition, path))
1146
1147 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1148 if args and args.strip():
1149 split_args = shlex.split(args)
1150 for index, arg in enumerate(split_args[:-1]):
1151 # Sanity check that the image file exists. Some images might be defined
1152 # as a path relative to source tree, which may not be available at the
1153 # same location when running this script (we have the input target_files
1154 # zip only). For such cases, we additionally scan other locations (e.g.
1155 # IMAGES/, RADIO/, etc) before bailing out.
1156 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001157 chained_image = split_args[index + 1]
1158 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001159 continue
1160 found = False
1161 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1162 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001163 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001164 if os.path.exists(alt_path):
1165 split_args[index + 1] = alt_path
1166 found = True
1167 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001168 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001169 cmd.extend(split_args)
1170
1171 RunAndCheckOutput(cmd)
1172
Tianjie Xueaed60c2020-03-12 00:33:28 -07001173 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001174 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001175 AddAftlInclusionProof(image_path)
1176
Daniel Norman276f0622019-07-26 14:13:51 -07001177
Steve Mucklee1b10862019-07-10 10:49:37 -07001178def _MakeRamdisk(sourcedir, fs_config_file=None):
1179 ramdisk_img = tempfile.NamedTemporaryFile()
1180
1181 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1182 cmd = ["mkbootfs", "-f", fs_config_file,
1183 os.path.join(sourcedir, "RAMDISK")]
1184 else:
1185 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1186 p1 = Run(cmd, stdout=subprocess.PIPE)
1187 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1188
1189 p2.wait()
1190 p1.wait()
1191 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1192 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1193
1194 return ramdisk_img
1195
1196
Steve Muckle9793cf62020-04-08 18:27:00 -07001197def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001198 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001199 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001200
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001201 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001202 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1203 we are building a two-step special image (i.e. building a recovery image to
1204 be loaded into /boot in two-step OTAs).
1205
1206 Return the image data, or None if sourcedir does not appear to contains files
1207 for building the requested image.
1208 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001209
Steve Muckle9793cf62020-04-08 18:27:00 -07001210 # "boot" or "recovery", without extension.
1211 partition_name = os.path.basename(sourcedir).lower()
1212
1213 if partition_name == "recovery":
1214 kernel = "kernel"
1215 else:
1216 kernel = image_name.replace("boot", "kernel")
1217 kernel = kernel.replace(".img","")
1218 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001219 return None
1220
1221 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001222 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001223
Doug Zongkerd5131602012-08-02 14:46:42 -07001224 if info_dict is None:
1225 info_dict = OPTIONS.info_dict
1226
Doug Zongkereef39442009-04-02 12:14:19 -07001227 img = tempfile.NamedTemporaryFile()
1228
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001229 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001230 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001231
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001232 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1233 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1234
Steve Muckle9793cf62020-04-08 18:27:00 -07001235 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001236
Benoit Fradina45a8682014-07-14 21:00:43 +02001237 fn = os.path.join(sourcedir, "second")
1238 if os.access(fn, os.F_OK):
1239 cmd.append("--second")
1240 cmd.append(fn)
1241
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001242 fn = os.path.join(sourcedir, "dtb")
1243 if os.access(fn, os.F_OK):
1244 cmd.append("--dtb")
1245 cmd.append(fn)
1246
Doug Zongker171f1cd2009-06-15 22:36:37 -07001247 fn = os.path.join(sourcedir, "cmdline")
1248 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001249 cmd.append("--cmdline")
1250 cmd.append(open(fn).read().rstrip("\n"))
1251
1252 fn = os.path.join(sourcedir, "base")
1253 if os.access(fn, os.F_OK):
1254 cmd.append("--base")
1255 cmd.append(open(fn).read().rstrip("\n"))
1256
Ying Wang4de6b5b2010-08-25 14:29:34 -07001257 fn = os.path.join(sourcedir, "pagesize")
1258 if os.access(fn, os.F_OK):
1259 cmd.append("--pagesize")
1260 cmd.append(open(fn).read().rstrip("\n"))
1261
Steve Mucklef84668e2020-03-16 19:13:46 -07001262 if partition_name == "recovery":
1263 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301264 if not args:
1265 # Fall back to "mkbootimg_args" for recovery image
1266 # in case "recovery_mkbootimg_args" is not set.
1267 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001268 else:
1269 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001270 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001271 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001272
Tao Bao76def242017-11-21 09:25:31 -08001273 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001274 if args and args.strip():
1275 cmd.extend(shlex.split(args))
1276
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001277 if has_ramdisk:
1278 cmd.extend(["--ramdisk", ramdisk_img.name])
1279
Tao Baod95e9fd2015-03-29 23:07:41 -07001280 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001281 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001282 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001283 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001284 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001285 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001286
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001287 if partition_name == "recovery":
1288 if info_dict.get("include_recovery_dtbo") == "true":
1289 fn = os.path.join(sourcedir, "recovery_dtbo")
1290 cmd.extend(["--recovery_dtbo", fn])
1291 if info_dict.get("include_recovery_acpio") == "true":
1292 fn = os.path.join(sourcedir, "recovery_acpio")
1293 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001294
Tao Bao986ee862018-10-04 15:46:16 -07001295 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001296
Tao Bao76def242017-11-21 09:25:31 -08001297 if (info_dict.get("boot_signer") == "true" and
1298 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001299 # Hard-code the path as "/boot" for two-step special recovery image (which
1300 # will be loaded into /boot during the two-step OTA).
1301 if two_step_image:
1302 path = "/boot"
1303 else:
Tao Baobf70c312017-07-11 17:27:55 -07001304 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001305 cmd = [OPTIONS.boot_signer_path]
1306 cmd.extend(OPTIONS.boot_signer_args)
1307 cmd.extend([path, img.name,
1308 info_dict["verity_key"] + ".pk8",
1309 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001310 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001311
Tao Baod95e9fd2015-03-29 23:07:41 -07001312 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001313 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001314 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001315 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001316 # We have switched from the prebuilt futility binary to using the tool
1317 # (futility-host) built from the source. Override the setting in the old
1318 # TF.zip.
1319 futility = info_dict["futility"]
1320 if futility.startswith("prebuilts/"):
1321 futility = "futility-host"
1322 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001323 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001324 info_dict["vboot_key"] + ".vbprivk",
1325 info_dict["vboot_subkey"] + ".vbprivk",
1326 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001327 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001328 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001329
Tao Baof3282b42015-04-01 11:21:55 -07001330 # Clean up the temp files.
1331 img_unsigned.close()
1332 img_keyblock.close()
1333
David Zeuthen8fecb282017-12-01 16:24:01 -05001334 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001335 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001336 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001337 if partition_name == "recovery":
1338 part_size = info_dict["recovery_size"]
1339 else:
1340 part_size = info_dict[image_name.replace(".img","_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001341 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001342 "--partition_size", str(part_size), "--partition_name",
1343 partition_name]
1344 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001345 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001346 if args and args.strip():
1347 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001348 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001349
1350 img.seek(os.SEEK_SET, 0)
1351 data = img.read()
1352
1353 if has_ramdisk:
1354 ramdisk_img.close()
1355 img.close()
1356
1357 return data
1358
1359
Doug Zongkerd5131602012-08-02 14:46:42 -07001360def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001361 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001362 """Return a File object with the desired bootable image.
1363
1364 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1365 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1366 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001367
Doug Zongker55d93282011-01-25 17:03:34 -08001368 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1369 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001370 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001371 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001372
1373 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1374 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001375 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001376 return File.FromLocalFile(name, prebuilt_path)
1377
Tao Bao32fcdab2018-10-12 10:30:39 -07001378 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001379
1380 if info_dict is None:
1381 info_dict = OPTIONS.info_dict
1382
1383 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001384 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1385 # for recovery.
1386 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1387 prebuilt_name != "boot.img" or
1388 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001389
Doug Zongker6f1d0312014-08-22 08:07:12 -07001390 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001391 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001392 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001393 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001394 if data:
1395 return File(name, data)
1396 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001397
Doug Zongkereef39442009-04-02 12:14:19 -07001398
Steve Mucklee1b10862019-07-10 10:49:37 -07001399def _BuildVendorBootImage(sourcedir, info_dict=None):
1400 """Build a vendor boot image from the specified sourcedir.
1401
1402 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1403 turn them into a vendor boot image.
1404
1405 Return the image data, or None if sourcedir does not appear to contains files
1406 for building the requested image.
1407 """
1408
1409 if info_dict is None:
1410 info_dict = OPTIONS.info_dict
1411
1412 img = tempfile.NamedTemporaryFile()
1413
1414 ramdisk_img = _MakeRamdisk(sourcedir)
1415
1416 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1417 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1418
1419 cmd = [mkbootimg]
1420
1421 fn = os.path.join(sourcedir, "dtb")
1422 if os.access(fn, os.F_OK):
1423 cmd.append("--dtb")
1424 cmd.append(fn)
1425
1426 fn = os.path.join(sourcedir, "vendor_cmdline")
1427 if os.access(fn, os.F_OK):
1428 cmd.append("--vendor_cmdline")
1429 cmd.append(open(fn).read().rstrip("\n"))
1430
1431 fn = os.path.join(sourcedir, "base")
1432 if os.access(fn, os.F_OK):
1433 cmd.append("--base")
1434 cmd.append(open(fn).read().rstrip("\n"))
1435
1436 fn = os.path.join(sourcedir, "pagesize")
1437 if os.access(fn, os.F_OK):
1438 cmd.append("--pagesize")
1439 cmd.append(open(fn).read().rstrip("\n"))
1440
1441 args = info_dict.get("mkbootimg_args")
1442 if args and args.strip():
1443 cmd.extend(shlex.split(args))
1444
1445 args = info_dict.get("mkbootimg_version_args")
1446 if args and args.strip():
1447 cmd.extend(shlex.split(args))
1448
1449 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1450 cmd.extend(["--vendor_boot", img.name])
1451
1452 RunAndCheckOutput(cmd)
1453
1454 # AVB: if enabled, calculate and add hash.
1455 if info_dict.get("avb_enable") == "true":
1456 avbtool = info_dict["avb_avbtool"]
1457 part_size = info_dict["vendor_boot_size"]
1458 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001459 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001460 AppendAVBSigningArgs(cmd, "vendor_boot")
1461 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1462 if args and args.strip():
1463 cmd.extend(shlex.split(args))
1464 RunAndCheckOutput(cmd)
1465
1466 img.seek(os.SEEK_SET, 0)
1467 data = img.read()
1468
1469 ramdisk_img.close()
1470 img.close()
1471
1472 return data
1473
1474
1475def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1476 info_dict=None):
1477 """Return a File object with the desired vendor boot image.
1478
1479 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1480 the source files in 'unpack_dir'/'tree_subdir'."""
1481
1482 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1483 if os.path.exists(prebuilt_path):
1484 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1485 return File.FromLocalFile(name, prebuilt_path)
1486
1487 logger.info("building image from target_files %s...", tree_subdir)
1488
1489 if info_dict is None:
1490 info_dict = OPTIONS.info_dict
1491
1492 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1493 if data:
1494 return File(name, data)
1495 return None
1496
1497
Narayan Kamatha07bf042017-08-14 14:49:21 +01001498def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001499 """Gunzips the given gzip compressed file to a given output file."""
1500 with gzip.open(in_filename, "rb") as in_file, \
1501 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001502 shutil.copyfileobj(in_file, out_file)
1503
1504
Tao Bao0ff15de2019-03-20 11:26:06 -07001505def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001506 """Unzips the archive to the given directory.
1507
1508 Args:
1509 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001510 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001511 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1512 archvie. Non-matching patterns will be filtered out. If there's no match
1513 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001514 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001515 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001516 if patterns is not None:
1517 # Filter out non-matching patterns. unzip will complain otherwise.
1518 with zipfile.ZipFile(filename) as input_zip:
1519 names = input_zip.namelist()
1520 filtered = [
1521 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1522
1523 # There isn't any matching files. Don't unzip anything.
1524 if not filtered:
1525 return
1526 cmd.extend(filtered)
1527
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001528 RunAndCheckOutput(cmd)
1529
1530
Doug Zongker75f17362009-12-08 13:46:44 -08001531def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001532 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001533
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001534 Args:
1535 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1536 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1537
1538 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1539 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001540
Tao Bao1c830bf2017-12-25 10:43:47 -08001541 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001542 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001543 """
Doug Zongkereef39442009-04-02 12:14:19 -07001544
Tao Bao1c830bf2017-12-25 10:43:47 -08001545 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001546 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1547 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001548 UnzipToDir(m.group(1), tmp, pattern)
1549 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001550 filename = m.group(1)
1551 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001552 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001553
Tao Baodba59ee2018-01-09 13:21:02 -08001554 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001555
1556
Yifan Hong8a66a712019-04-04 15:37:57 -07001557def GetUserImage(which, tmpdir, input_zip,
1558 info_dict=None,
1559 allow_shared_blocks=None,
1560 hashtree_info_generator=None,
1561 reset_file_map=False):
1562 """Returns an Image object suitable for passing to BlockImageDiff.
1563
1564 This function loads the specified image from the given path. If the specified
1565 image is sparse, it also performs additional processing for OTA purpose. For
1566 example, it always adds block 0 to clobbered blocks list. It also detects
1567 files that cannot be reconstructed from the block list, for whom we should
1568 avoid applying imgdiff.
1569
1570 Args:
1571 which: The partition name.
1572 tmpdir: The directory that contains the prebuilt image and block map file.
1573 input_zip: The target-files ZIP archive.
1574 info_dict: The dict to be looked up for relevant info.
1575 allow_shared_blocks: If image is sparse, whether having shared blocks is
1576 allowed. If none, it is looked up from info_dict.
1577 hashtree_info_generator: If present and image is sparse, generates the
1578 hashtree_info for this sparse image.
1579 reset_file_map: If true and image is sparse, reset file map before returning
1580 the image.
1581 Returns:
1582 A Image object. If it is a sparse image and reset_file_map is False, the
1583 image will have file_map info loaded.
1584 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001585 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001586 info_dict = LoadInfoDict(input_zip)
1587
1588 is_sparse = info_dict.get("extfs_sparse_flag")
1589
1590 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1591 # shared blocks (i.e. some blocks will show up in multiple files' block
1592 # list). We can only allocate such shared blocks to the first "owner", and
1593 # disable imgdiff for all later occurrences.
1594 if allow_shared_blocks is None:
1595 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1596
1597 if is_sparse:
1598 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1599 hashtree_info_generator)
1600 if reset_file_map:
1601 img.ResetFileMap()
1602 return img
1603 else:
1604 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1605
1606
1607def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1608 """Returns a Image object suitable for passing to BlockImageDiff.
1609
1610 This function loads the specified non-sparse image from the given path.
1611
1612 Args:
1613 which: The partition name.
1614 tmpdir: The directory that contains the prebuilt image and block map file.
1615 Returns:
1616 A Image object.
1617 """
1618 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1619 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1620
1621 # The image and map files must have been created prior to calling
1622 # ota_from_target_files.py (since LMP).
1623 assert os.path.exists(path) and os.path.exists(mappath)
1624
Tianjie Xu41976c72019-07-03 13:57:01 -07001625 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1626
Yifan Hong8a66a712019-04-04 15:37:57 -07001627
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001628def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1629 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001630 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1631
1632 This function loads the specified sparse image from the given path, and
1633 performs additional processing for OTA purpose. For example, it always adds
1634 block 0 to clobbered blocks list. It also detects files that cannot be
1635 reconstructed from the block list, for whom we should avoid applying imgdiff.
1636
1637 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001638 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001639 tmpdir: The directory that contains the prebuilt image and block map file.
1640 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001641 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001642 hashtree_info_generator: If present, generates the hashtree_info for this
1643 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001644 Returns:
1645 A SparseImage object, with file_map info loaded.
1646 """
Tao Baoc765cca2018-01-31 17:32:40 -08001647 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1648 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1649
1650 # The image and map files must have been created prior to calling
1651 # ota_from_target_files.py (since LMP).
1652 assert os.path.exists(path) and os.path.exists(mappath)
1653
1654 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1655 # it to clobbered_blocks so that it will be written to the target
1656 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1657 clobbered_blocks = "0"
1658
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001659 image = sparse_img.SparseImage(
1660 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1661 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001662
1663 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1664 # if they contain all zeros. We can't reconstruct such a file from its block
1665 # list. Tag such entries accordingly. (Bug: 65213616)
1666 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001667 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001668 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001669 continue
1670
Tom Cherryd14b8952018-08-09 14:26:00 -07001671 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1672 # filename listed in system.map may contain an additional leading slash
1673 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1674 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001675 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001676
Tom Cherryd14b8952018-08-09 14:26:00 -07001677 # Special handling another case, where files not under /system
1678 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001679 if which == 'system' and not arcname.startswith('SYSTEM'):
1680 arcname = 'ROOT/' + arcname
1681
1682 assert arcname in input_zip.namelist(), \
1683 "Failed to find the ZIP entry for {}".format(entry)
1684
Tao Baoc765cca2018-01-31 17:32:40 -08001685 info = input_zip.getinfo(arcname)
1686 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001687
1688 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001689 # image, check the original block list to determine its completeness. Note
1690 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001691 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001692 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001693
Tao Baoc765cca2018-01-31 17:32:40 -08001694 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1695 ranges.extra['incomplete'] = True
1696
1697 return image
1698
1699
Doug Zongkereef39442009-04-02 12:14:19 -07001700def GetKeyPasswords(keylist):
1701 """Given a list of keys, prompt the user to enter passwords for
1702 those which require them. Return a {key: password} dict. password
1703 will be None if the key has no password."""
1704
Doug Zongker8ce7c252009-05-22 13:34:54 -07001705 no_passwords = []
1706 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001707 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001708 devnull = open("/dev/null", "w+b")
1709 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001710 # We don't need a password for things that aren't really keys.
1711 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001712 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001713 continue
1714
T.R. Fullhart37e10522013-03-18 10:31:26 -07001715 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001716 "-inform", "DER", "-nocrypt"],
1717 stdin=devnull.fileno(),
1718 stdout=devnull.fileno(),
1719 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001720 p.communicate()
1721 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001722 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001723 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001724 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001725 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1726 "-inform", "DER", "-passin", "pass:"],
1727 stdin=devnull.fileno(),
1728 stdout=devnull.fileno(),
1729 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001730 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001731 if p.returncode == 0:
1732 # Encrypted key with empty string as password.
1733 key_passwords[k] = ''
1734 elif stderr.startswith('Error decrypting key'):
1735 # Definitely encrypted key.
1736 # It would have said "Error reading key" if it didn't parse correctly.
1737 need_passwords.append(k)
1738 else:
1739 # Potentially, a type of key that openssl doesn't understand.
1740 # We'll let the routines in signapk.jar handle it.
1741 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001742 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001743
T.R. Fullhart37e10522013-03-18 10:31:26 -07001744 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001745 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001746 return key_passwords
1747
1748
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001749def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001750 """Gets the minSdkVersion declared in the APK.
1751
changho.shin0f125362019-07-08 10:59:00 +09001752 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001753 This can be both a decimal number (API Level) or a codename.
1754
1755 Args:
1756 apk_name: The APK filename.
1757
1758 Returns:
1759 The parsed SDK version string.
1760
1761 Raises:
1762 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001763 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001764 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001765 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001766 stderr=subprocess.PIPE)
1767 stdoutdata, stderrdata = proc.communicate()
1768 if proc.returncode != 0:
1769 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001770 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001771 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001772
Tao Baof47bf0f2018-03-21 23:28:51 -07001773 for line in stdoutdata.split("\n"):
1774 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001775 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1776 if m:
1777 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001778 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001779
1780
1781def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001782 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001783
Tao Baof47bf0f2018-03-21 23:28:51 -07001784 If minSdkVersion is set to a codename, it is translated to a number using the
1785 provided map.
1786
1787 Args:
1788 apk_name: The APK filename.
1789
1790 Returns:
1791 The parsed SDK version number.
1792
1793 Raises:
1794 ExternalError: On failing to get the min SDK version number.
1795 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001796 version = GetMinSdkVersion(apk_name)
1797 try:
1798 return int(version)
1799 except ValueError:
1800 # Not a decimal number. Codename?
1801 if version in codename_to_api_level_map:
1802 return codename_to_api_level_map[version]
1803 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001804 raise ExternalError(
1805 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1806 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001807
1808
1809def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001810 codename_to_api_level_map=None, whole_file=False,
1811 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001812 """Sign the input_name zip/jar/apk, producing output_name. Use the
1813 given key and password (the latter may be None if the key does not
1814 have a password.
1815
Doug Zongker951495f2009-08-14 12:44:19 -07001816 If whole_file is true, use the "-w" option to SignApk to embed a
1817 signature that covers the whole file in the archive comment of the
1818 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001819
1820 min_api_level is the API Level (int) of the oldest platform this file may end
1821 up on. If not specified for an APK, the API Level is obtained by interpreting
1822 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1823
1824 codename_to_api_level_map is needed to translate the codename which may be
1825 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001826
1827 Caller may optionally specify extra args to be passed to SignApk, which
1828 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001829 """
Tao Bao76def242017-11-21 09:25:31 -08001830 if codename_to_api_level_map is None:
1831 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001832 if extra_signapk_args is None:
1833 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001834
Alex Klyubin9667b182015-12-10 13:38:50 -08001835 java_library_path = os.path.join(
1836 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1837
Tao Baoe95540e2016-11-08 12:08:53 -08001838 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1839 ["-Djava.library.path=" + java_library_path,
1840 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001841 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001842 if whole_file:
1843 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001844
1845 min_sdk_version = min_api_level
1846 if min_sdk_version is None:
1847 if not whole_file:
1848 min_sdk_version = GetMinSdkVersionInt(
1849 input_name, codename_to_api_level_map)
1850 if min_sdk_version is not None:
1851 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1852
T.R. Fullhart37e10522013-03-18 10:31:26 -07001853 cmd.extend([key + OPTIONS.public_key_suffix,
1854 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001855 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001856
Tao Bao73dd4f42018-10-04 16:25:33 -07001857 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001858 if password is not None:
1859 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001860 stdoutdata, _ = proc.communicate(password)
1861 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001862 raise ExternalError(
1863 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001864 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001865
Doug Zongkereef39442009-04-02 12:14:19 -07001866
Doug Zongker37974732010-09-16 17:44:38 -07001867def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001868 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001869
Tao Bao9dd909e2017-11-14 11:27:32 -08001870 For non-AVB images, raise exception if the data is too big. Print a warning
1871 if the data is nearing the maximum size.
1872
1873 For AVB images, the actual image size should be identical to the limit.
1874
1875 Args:
1876 data: A string that contains all the data for the partition.
1877 target: The partition name. The ".img" suffix is optional.
1878 info_dict: The dict to be looked up for relevant info.
1879 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001880 if target.endswith(".img"):
1881 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001882 mount_point = "/" + target
1883
Ying Wangf8824af2014-06-03 14:07:27 -07001884 fs_type = None
1885 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001886 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001887 if mount_point == "/userdata":
1888 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001889 p = info_dict["fstab"][mount_point]
1890 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001891 device = p.device
1892 if "/" in device:
1893 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001894 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001895 if not fs_type or not limit:
1896 return
Doug Zongkereef39442009-04-02 12:14:19 -07001897
Andrew Boie0f9aec82012-02-14 09:32:52 -08001898 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001899 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1900 # path.
1901 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1902 if size != limit:
1903 raise ExternalError(
1904 "Mismatching image size for %s: expected %d actual %d" % (
1905 target, limit, size))
1906 else:
1907 pct = float(size) * 100.0 / limit
1908 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1909 if pct >= 99.0:
1910 raise ExternalError(msg)
1911 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001912 logger.warning("\n WARNING: %s\n", msg)
1913 else:
1914 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001915
1916
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001917def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001918 """Parses the APK certs info from a given target-files zip.
1919
1920 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1921 tuple with the following elements: (1) a dictionary that maps packages to
1922 certs (based on the "certificate" and "private_key" attributes in the file;
1923 (2) a string representing the extension of compressed APKs in the target files
1924 (e.g ".gz", ".bro").
1925
1926 Args:
1927 tf_zip: The input target_files ZipFile (already open).
1928
1929 Returns:
1930 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1931 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1932 no compressed APKs.
1933 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001934 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001935 compressed_extension = None
1936
Tao Bao0f990332017-09-08 19:02:54 -07001937 # META/apkcerts.txt contains the info for _all_ the packages known at build
1938 # time. Filter out the ones that are not installed.
1939 installed_files = set()
1940 for name in tf_zip.namelist():
1941 basename = os.path.basename(name)
1942 if basename:
1943 installed_files.add(basename)
1944
Tao Baoda30cfa2017-12-01 16:19:46 -08001945 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001946 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001947 if not line:
1948 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001949 m = re.match(
1950 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001951 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1952 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001953 line)
1954 if not m:
1955 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001956
Tao Bao818ddf52018-01-05 11:17:34 -08001957 matches = m.groupdict()
1958 cert = matches["CERT"]
1959 privkey = matches["PRIVKEY"]
1960 name = matches["NAME"]
1961 this_compressed_extension = matches["COMPRESSED"]
1962
1963 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1964 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1965 if cert in SPECIAL_CERT_STRINGS and not privkey:
1966 certmap[name] = cert
1967 elif (cert.endswith(OPTIONS.public_key_suffix) and
1968 privkey.endswith(OPTIONS.private_key_suffix) and
1969 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1970 certmap[name] = cert[:-public_key_suffix_len]
1971 else:
1972 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1973
1974 if not this_compressed_extension:
1975 continue
1976
1977 # Only count the installed files.
1978 filename = name + '.' + this_compressed_extension
1979 if filename not in installed_files:
1980 continue
1981
1982 # Make sure that all the values in the compression map have the same
1983 # extension. We don't support multiple compression methods in the same
1984 # system image.
1985 if compressed_extension:
1986 if this_compressed_extension != compressed_extension:
1987 raise ValueError(
1988 "Multiple compressed extensions: {} vs {}".format(
1989 compressed_extension, this_compressed_extension))
1990 else:
1991 compressed_extension = this_compressed_extension
1992
1993 return (certmap,
1994 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001995
1996
Doug Zongkereef39442009-04-02 12:14:19 -07001997COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001998Global options
1999
2000 -p (--path) <dir>
2001 Prepend <dir>/bin to the list of places to search for binaries run by this
2002 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002003
Doug Zongker05d3dea2009-06-22 11:32:31 -07002004 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002005 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002006
Tao Bao30df8b42018-04-23 15:32:53 -07002007 -x (--extra) <key=value>
2008 Add a key/value pair to the 'extras' dict, which device-specific extension
2009 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002010
Doug Zongkereef39442009-04-02 12:14:19 -07002011 -v (--verbose)
2012 Show command lines being executed.
2013
2014 -h (--help)
2015 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002016
2017 --logfile <file>
2018 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002019"""
2020
2021def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002022 print(docstring.rstrip("\n"))
2023 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002024
2025
2026def ParseOptions(argv,
2027 docstring,
2028 extra_opts="", extra_long_opts=(),
2029 extra_option_handler=None):
2030 """Parse the options in argv and return any arguments that aren't
2031 flags. docstring is the calling module's docstring, to be displayed
2032 for errors and -h. extra_opts and extra_long_opts are for flags
2033 defined by the caller, which are processed by passing them to
2034 extra_option_handler."""
2035
2036 try:
2037 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002038 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002039 ["help", "verbose", "path=", "signapk_path=",
2040 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002041 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002042 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2043 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002044 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2045 "aftl_key_path=", "aftl_manufacturer_key_path=",
2046 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002047 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002048 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002049 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002050 sys.exit(2)
2051
Doug Zongkereef39442009-04-02 12:14:19 -07002052 for o, a in opts:
2053 if o in ("-h", "--help"):
2054 Usage(docstring)
2055 sys.exit()
2056 elif o in ("-v", "--verbose"):
2057 OPTIONS.verbose = True
2058 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002059 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002060 elif o in ("--signapk_path",):
2061 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002062 elif o in ("--signapk_shared_library_path",):
2063 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002064 elif o in ("--extra_signapk_args",):
2065 OPTIONS.extra_signapk_args = shlex.split(a)
2066 elif o in ("--java_path",):
2067 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002068 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002069 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002070 elif o in ("--android_jar_path",):
2071 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002072 elif o in ("--public_key_suffix",):
2073 OPTIONS.public_key_suffix = a
2074 elif o in ("--private_key_suffix",):
2075 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002076 elif o in ("--boot_signer_path",):
2077 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002078 elif o in ("--boot_signer_args",):
2079 OPTIONS.boot_signer_args = shlex.split(a)
2080 elif o in ("--verity_signer_path",):
2081 OPTIONS.verity_signer_path = a
2082 elif o in ("--verity_signer_args",):
2083 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002084 elif o in ("--aftl_tool_path",):
2085 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002086 elif o in ("--aftl_server",):
2087 OPTIONS.aftl_server = a
2088 elif o in ("--aftl_key_path",):
2089 OPTIONS.aftl_key_path = a
2090 elif o in ("--aftl_manufacturer_key_path",):
2091 OPTIONS.aftl_manufacturer_key_path = a
2092 elif o in ("--aftl_signer_helper",):
2093 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002094 elif o in ("-s", "--device_specific"):
2095 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002096 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002097 key, value = a.split("=", 1)
2098 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002099 elif o in ("--logfile",):
2100 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002101 else:
2102 if extra_option_handler is None or not extra_option_handler(o, a):
2103 assert False, "unknown option \"%s\"" % (o,)
2104
Doug Zongker85448772014-09-09 14:59:20 -07002105 if OPTIONS.search_path:
2106 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2107 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002108
2109 return args
2110
2111
Tao Bao4c851b12016-09-19 13:54:38 -07002112def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002113 """Make a temp file and add it to the list of things to be deleted
2114 when Cleanup() is called. Return the filename."""
2115 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2116 os.close(fd)
2117 OPTIONS.tempfiles.append(fn)
2118 return fn
2119
2120
Tao Bao1c830bf2017-12-25 10:43:47 -08002121def MakeTempDir(prefix='tmp', suffix=''):
2122 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2123
2124 Returns:
2125 The absolute pathname of the new directory.
2126 """
2127 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2128 OPTIONS.tempfiles.append(dir_name)
2129 return dir_name
2130
2131
Doug Zongkereef39442009-04-02 12:14:19 -07002132def Cleanup():
2133 for i in OPTIONS.tempfiles:
2134 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002135 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002136 else:
2137 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002138 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002139
2140
2141class PasswordManager(object):
2142 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002143 self.editor = os.getenv("EDITOR")
2144 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002145
2146 def GetPasswords(self, items):
2147 """Get passwords corresponding to each string in 'items',
2148 returning a dict. (The dict may have keys in addition to the
2149 values in 'items'.)
2150
2151 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2152 user edit that file to add more needed passwords. If no editor is
2153 available, or $ANDROID_PW_FILE isn't define, prompts the user
2154 interactively in the ordinary way.
2155 """
2156
2157 current = self.ReadFile()
2158
2159 first = True
2160 while True:
2161 missing = []
2162 for i in items:
2163 if i not in current or not current[i]:
2164 missing.append(i)
2165 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002166 if not missing:
2167 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002168
2169 for i in missing:
2170 current[i] = ""
2171
2172 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002173 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002174 if sys.version_info[0] >= 3:
2175 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002176 answer = raw_input("try to edit again? [y]> ").strip()
2177 if answer and answer[0] not in 'yY':
2178 raise RuntimeError("key passwords unavailable")
2179 first = False
2180
2181 current = self.UpdateAndReadFile(current)
2182
Dan Albert8b72aef2015-03-23 19:13:21 -07002183 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002184 """Prompt the user to enter a value (password) for each key in
2185 'current' whose value is fales. Returns a new dict with all the
2186 values.
2187 """
2188 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002189 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002190 if v:
2191 result[k] = v
2192 else:
2193 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002194 result[k] = getpass.getpass(
2195 "Enter password for %s key> " % k).strip()
2196 if result[k]:
2197 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002198 return result
2199
2200 def UpdateAndReadFile(self, current):
2201 if not self.editor or not self.pwfile:
2202 return self.PromptResult(current)
2203
2204 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002205 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002206 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2207 f.write("# (Additional spaces are harmless.)\n\n")
2208
2209 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002210 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002211 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002212 f.write("[[[ %s ]]] %s\n" % (v, k))
2213 if not v and first_line is None:
2214 # position cursor on first line with no password.
2215 first_line = i + 4
2216 f.close()
2217
Tao Bao986ee862018-10-04 15:46:16 -07002218 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002219
2220 return self.ReadFile()
2221
2222 def ReadFile(self):
2223 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002224 if self.pwfile is None:
2225 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002226 try:
2227 f = open(self.pwfile, "r")
2228 for line in f:
2229 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002230 if not line or line[0] == '#':
2231 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002232 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2233 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002234 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002235 else:
2236 result[m.group(2)] = m.group(1)
2237 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002238 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002239 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002240 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002241 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002242
2243
Dan Albert8e0178d2015-01-27 15:53:15 -08002244def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2245 compress_type=None):
2246 import datetime
2247
2248 # http://b/18015246
2249 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2250 # for files larger than 2GiB. We can work around this by adjusting their
2251 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2252 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2253 # it isn't clear to me exactly what circumstances cause this).
2254 # `zipfile.write()` must be used directly to work around this.
2255 #
2256 # This mess can be avoided if we port to python3.
2257 saved_zip64_limit = zipfile.ZIP64_LIMIT
2258 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2259
2260 if compress_type is None:
2261 compress_type = zip_file.compression
2262 if arcname is None:
2263 arcname = filename
2264
2265 saved_stat = os.stat(filename)
2266
2267 try:
2268 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2269 # file to be zipped and reset it when we're done.
2270 os.chmod(filename, perms)
2271
2272 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002273 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2274 # intentional. zip stores datetimes in local time without a time zone
2275 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2276 # in the zip archive.
2277 local_epoch = datetime.datetime.fromtimestamp(0)
2278 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002279 os.utime(filename, (timestamp, timestamp))
2280
2281 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2282 finally:
2283 os.chmod(filename, saved_stat.st_mode)
2284 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2285 zipfile.ZIP64_LIMIT = saved_zip64_limit
2286
2287
Tao Bao58c1b962015-05-20 09:32:18 -07002288def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002289 compress_type=None):
2290 """Wrap zipfile.writestr() function to work around the zip64 limit.
2291
2292 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2293 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2294 when calling crc32(bytes).
2295
2296 But it still works fine to write a shorter string into a large zip file.
2297 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2298 when we know the string won't be too long.
2299 """
2300
2301 saved_zip64_limit = zipfile.ZIP64_LIMIT
2302 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2303
2304 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2305 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002306 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002307 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002308 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002309 else:
Tao Baof3282b42015-04-01 11:21:55 -07002310 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002311 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2312 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2313 # such a case (since
2314 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2315 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2316 # permission bits. We follow the logic in Python 3 to get consistent
2317 # behavior between using the two versions.
2318 if not zinfo.external_attr:
2319 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002320
2321 # If compress_type is given, it overrides the value in zinfo.
2322 if compress_type is not None:
2323 zinfo.compress_type = compress_type
2324
Tao Bao58c1b962015-05-20 09:32:18 -07002325 # If perms is given, it has a priority.
2326 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002327 # If perms doesn't set the file type, mark it as a regular file.
2328 if perms & 0o770000 == 0:
2329 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002330 zinfo.external_attr = perms << 16
2331
Tao Baof3282b42015-04-01 11:21:55 -07002332 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002333 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2334
Dan Albert8b72aef2015-03-23 19:13:21 -07002335 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002336 zipfile.ZIP64_LIMIT = saved_zip64_limit
2337
2338
Tao Bao89d7ab22017-12-14 17:05:33 -08002339def ZipDelete(zip_filename, entries):
2340 """Deletes entries from a ZIP file.
2341
2342 Since deleting entries from a ZIP file is not supported, it shells out to
2343 'zip -d'.
2344
2345 Args:
2346 zip_filename: The name of the ZIP file.
2347 entries: The name of the entry, or the list of names to be deleted.
2348
2349 Raises:
2350 AssertionError: In case of non-zero return from 'zip'.
2351 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002352 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002353 entries = [entries]
2354 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002355 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002356
2357
Tao Baof3282b42015-04-01 11:21:55 -07002358def ZipClose(zip_file):
2359 # http://b/18015246
2360 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2361 # central directory.
2362 saved_zip64_limit = zipfile.ZIP64_LIMIT
2363 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2364
2365 zip_file.close()
2366
2367 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002368
2369
2370class DeviceSpecificParams(object):
2371 module = None
2372 def __init__(self, **kwargs):
2373 """Keyword arguments to the constructor become attributes of this
2374 object, which is passed to all functions in the device-specific
2375 module."""
Tao Bao38884282019-07-10 22:20:56 -07002376 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002377 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002378 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002379
2380 if self.module is None:
2381 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002382 if not path:
2383 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002384 try:
2385 if os.path.isdir(path):
2386 info = imp.find_module("releasetools", [path])
2387 else:
2388 d, f = os.path.split(path)
2389 b, x = os.path.splitext(f)
2390 if x == ".py":
2391 f = b
2392 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002393 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002394 self.module = imp.load_module("device_specific", *info)
2395 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002396 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002397
2398 def _DoCall(self, function_name, *args, **kwargs):
2399 """Call the named function in the device-specific module, passing
2400 the given args and kwargs. The first argument to the call will be
2401 the DeviceSpecific object itself. If there is no module, or the
2402 module does not define the function, return the value of the
2403 'default' kwarg (which itself defaults to None)."""
2404 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002405 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002406 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2407
2408 def FullOTA_Assertions(self):
2409 """Called after emitting the block of assertions at the top of a
2410 full OTA package. Implementations can add whatever additional
2411 assertions they like."""
2412 return self._DoCall("FullOTA_Assertions")
2413
Doug Zongkere5ff5902012-01-17 10:55:37 -08002414 def FullOTA_InstallBegin(self):
2415 """Called at the start of full OTA installation."""
2416 return self._DoCall("FullOTA_InstallBegin")
2417
Yifan Hong10c530d2018-12-27 17:34:18 -08002418 def FullOTA_GetBlockDifferences(self):
2419 """Called during full OTA installation and verification.
2420 Implementation should return a list of BlockDifference objects describing
2421 the update on each additional partitions.
2422 """
2423 return self._DoCall("FullOTA_GetBlockDifferences")
2424
Doug Zongker05d3dea2009-06-22 11:32:31 -07002425 def FullOTA_InstallEnd(self):
2426 """Called at the end of full OTA installation; typically this is
2427 used to install the image for the device's baseband processor."""
2428 return self._DoCall("FullOTA_InstallEnd")
2429
2430 def IncrementalOTA_Assertions(self):
2431 """Called after emitting the block of assertions at the top of an
2432 incremental OTA package. Implementations can add whatever
2433 additional assertions they like."""
2434 return self._DoCall("IncrementalOTA_Assertions")
2435
Doug Zongkere5ff5902012-01-17 10:55:37 -08002436 def IncrementalOTA_VerifyBegin(self):
2437 """Called at the start of the verification phase of incremental
2438 OTA installation; additional checks can be placed here to abort
2439 the script before any changes are made."""
2440 return self._DoCall("IncrementalOTA_VerifyBegin")
2441
Doug Zongker05d3dea2009-06-22 11:32:31 -07002442 def IncrementalOTA_VerifyEnd(self):
2443 """Called at the end of the verification phase of incremental OTA
2444 installation; additional checks can be placed here to abort the
2445 script before any changes are made."""
2446 return self._DoCall("IncrementalOTA_VerifyEnd")
2447
Doug Zongkere5ff5902012-01-17 10:55:37 -08002448 def IncrementalOTA_InstallBegin(self):
2449 """Called at the start of incremental OTA installation (after
2450 verification is complete)."""
2451 return self._DoCall("IncrementalOTA_InstallBegin")
2452
Yifan Hong10c530d2018-12-27 17:34:18 -08002453 def IncrementalOTA_GetBlockDifferences(self):
2454 """Called during incremental OTA installation and verification.
2455 Implementation should return a list of BlockDifference objects describing
2456 the update on each additional partitions.
2457 """
2458 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2459
Doug Zongker05d3dea2009-06-22 11:32:31 -07002460 def IncrementalOTA_InstallEnd(self):
2461 """Called at the end of incremental OTA installation; typically
2462 this is used to install the image for the device's baseband
2463 processor."""
2464 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002465
Tao Bao9bc6bb22015-11-09 16:58:28 -08002466 def VerifyOTA_Assertions(self):
2467 return self._DoCall("VerifyOTA_Assertions")
2468
Tao Bao76def242017-11-21 09:25:31 -08002469
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002470class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002471 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002472 self.name = name
2473 self.data = data
2474 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002475 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002476 self.sha1 = sha1(data).hexdigest()
2477
2478 @classmethod
2479 def FromLocalFile(cls, name, diskname):
2480 f = open(diskname, "rb")
2481 data = f.read()
2482 f.close()
2483 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002484
2485 def WriteToTemp(self):
2486 t = tempfile.NamedTemporaryFile()
2487 t.write(self.data)
2488 t.flush()
2489 return t
2490
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002491 def WriteToDir(self, d):
2492 with open(os.path.join(d, self.name), "wb") as fp:
2493 fp.write(self.data)
2494
Geremy Condra36bd3652014-02-06 19:45:10 -08002495 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002496 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002497
Tao Bao76def242017-11-21 09:25:31 -08002498
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002499DIFF_PROGRAM_BY_EXT = {
2500 ".gz" : "imgdiff",
2501 ".zip" : ["imgdiff", "-z"],
2502 ".jar" : ["imgdiff", "-z"],
2503 ".apk" : ["imgdiff", "-z"],
2504 ".img" : "imgdiff",
2505 }
2506
Tao Bao76def242017-11-21 09:25:31 -08002507
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002508class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002509 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002510 self.tf = tf
2511 self.sf = sf
2512 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002513 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002514
2515 def ComputePatch(self):
2516 """Compute the patch (as a string of data) needed to turn sf into
2517 tf. Returns the same tuple as GetPatch()."""
2518
2519 tf = self.tf
2520 sf = self.sf
2521
Doug Zongker24cd2802012-08-14 16:36:15 -07002522 if self.diff_program:
2523 diff_program = self.diff_program
2524 else:
2525 ext = os.path.splitext(tf.name)[1]
2526 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002527
2528 ttemp = tf.WriteToTemp()
2529 stemp = sf.WriteToTemp()
2530
2531 ext = os.path.splitext(tf.name)[1]
2532
2533 try:
2534 ptemp = tempfile.NamedTemporaryFile()
2535 if isinstance(diff_program, list):
2536 cmd = copy.copy(diff_program)
2537 else:
2538 cmd = [diff_program]
2539 cmd.append(stemp.name)
2540 cmd.append(ttemp.name)
2541 cmd.append(ptemp.name)
2542 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002543 err = []
2544 def run():
2545 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002546 if e:
2547 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002548 th = threading.Thread(target=run)
2549 th.start()
2550 th.join(timeout=300) # 5 mins
2551 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002552 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002553 p.terminate()
2554 th.join(5)
2555 if th.is_alive():
2556 p.kill()
2557 th.join()
2558
Tianjie Xua2a9f992018-01-05 15:15:54 -08002559 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002560 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002561 self.patch = None
2562 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002563 diff = ptemp.read()
2564 finally:
2565 ptemp.close()
2566 stemp.close()
2567 ttemp.close()
2568
2569 self.patch = diff
2570 return self.tf, self.sf, self.patch
2571
2572
2573 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002574 """Returns a tuple of (target_file, source_file, patch_data).
2575
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002576 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002577 computing the patch failed.
2578 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002579 return self.tf, self.sf, self.patch
2580
2581
2582def ComputeDifferences(diffs):
2583 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002584 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002585
2586 # Do the largest files first, to try and reduce the long-pole effect.
2587 by_size = [(i.tf.size, i) for i in diffs]
2588 by_size.sort(reverse=True)
2589 by_size = [i[1] for i in by_size]
2590
2591 lock = threading.Lock()
2592 diff_iter = iter(by_size) # accessed under lock
2593
2594 def worker():
2595 try:
2596 lock.acquire()
2597 for d in diff_iter:
2598 lock.release()
2599 start = time.time()
2600 d.ComputePatch()
2601 dur = time.time() - start
2602 lock.acquire()
2603
2604 tf, sf, patch = d.GetPatch()
2605 if sf.name == tf.name:
2606 name = tf.name
2607 else:
2608 name = "%s (%s)" % (tf.name, sf.name)
2609 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002610 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002611 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002612 logger.info(
2613 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2614 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002615 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002616 except Exception:
2617 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002618 raise
2619
2620 # start worker threads; wait for them all to finish.
2621 threads = [threading.Thread(target=worker)
2622 for i in range(OPTIONS.worker_threads)]
2623 for th in threads:
2624 th.start()
2625 while threads:
2626 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002627
2628
Dan Albert8b72aef2015-03-23 19:13:21 -07002629class BlockDifference(object):
2630 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002631 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002632 self.tgt = tgt
2633 self.src = src
2634 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002635 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002636 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002637
Tao Baodd2a5892015-03-12 12:32:37 -07002638 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002639 version = max(
2640 int(i) for i in
2641 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002642 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002643 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002644
Tianjie Xu41976c72019-07-03 13:57:01 -07002645 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2646 version=self.version,
2647 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002648 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002649 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002650 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002651 self.touched_src_ranges = b.touched_src_ranges
2652 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002653
Yifan Hong10c530d2018-12-27 17:34:18 -08002654 # On devices with dynamic partitions, for new partitions,
2655 # src is None but OPTIONS.source_info_dict is not.
2656 if OPTIONS.source_info_dict is None:
2657 is_dynamic_build = OPTIONS.info_dict.get(
2658 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002659 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002660 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002661 is_dynamic_build = OPTIONS.source_info_dict.get(
2662 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002663 is_dynamic_source = partition in shlex.split(
2664 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002665
Yifan Hongbb2658d2019-01-25 12:30:58 -08002666 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002667 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2668
Yifan Hongbb2658d2019-01-25 12:30:58 -08002669 # For dynamic partitions builds, check partition list in both source
2670 # and target build because new partitions may be added, and existing
2671 # partitions may be removed.
2672 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2673
Yifan Hong10c530d2018-12-27 17:34:18 -08002674 if is_dynamic:
2675 self.device = 'map_partition("%s")' % partition
2676 else:
2677 if OPTIONS.source_info_dict is None:
2678 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2679 else:
2680 _, device_path = GetTypeAndDevice("/" + partition,
2681 OPTIONS.source_info_dict)
2682 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002683
Tao Baod8d14be2016-02-04 14:26:02 -08002684 @property
2685 def required_cache(self):
2686 return self._required_cache
2687
Tao Bao76def242017-11-21 09:25:31 -08002688 def WriteScript(self, script, output_zip, progress=None,
2689 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002690 if not self.src:
2691 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002692 script.Print("Patching %s image unconditionally..." % (self.partition,))
2693 else:
2694 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002695
Dan Albert8b72aef2015-03-23 19:13:21 -07002696 if progress:
2697 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002698 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002699
2700 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002701 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002702
Tao Bao9bc6bb22015-11-09 16:58:28 -08002703 def WriteStrictVerifyScript(self, script):
2704 """Verify all the blocks in the care_map, including clobbered blocks.
2705
2706 This differs from the WriteVerifyScript() function: a) it prints different
2707 error messages; b) it doesn't allow half-way updated images to pass the
2708 verification."""
2709
2710 partition = self.partition
2711 script.Print("Verifying %s..." % (partition,))
2712 ranges = self.tgt.care_map
2713 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002714 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002715 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2716 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002717 self.device, ranges_str,
2718 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002719 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002720 script.AppendExtra("")
2721
Tao Baod522bdc2016-04-12 15:53:16 -07002722 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002723 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002724
2725 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002726 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002727 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002728
2729 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002730 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002731 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002732 ranges = self.touched_src_ranges
2733 expected_sha1 = self.touched_src_sha1
2734 else:
2735 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2736 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002737
2738 # No blocks to be checked, skipping.
2739 if not ranges:
2740 return
2741
Tao Bao5ece99d2015-05-12 11:42:31 -07002742 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002743 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002744 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002745 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2746 '"%s.patch.dat")) then' % (
2747 self.device, ranges_str, expected_sha1,
2748 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002749 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002750 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002751
Tianjie Xufc3422a2015-12-15 11:53:59 -08002752 if self.version >= 4:
2753
2754 # Bug: 21124327
2755 # When generating incrementals for the system and vendor partitions in
2756 # version 4 or newer, explicitly check the first block (which contains
2757 # the superblock) of the partition to see if it's what we expect. If
2758 # this check fails, give an explicit log message about the partition
2759 # having been remounted R/W (the most likely explanation).
2760 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002761 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002762
2763 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002764 if partition == "system":
2765 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2766 else:
2767 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002768 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002769 'ifelse (block_image_recover({device}, "{ranges}") && '
2770 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002771 'package_extract_file("{partition}.transfer.list"), '
2772 '"{partition}.new.dat", "{partition}.patch.dat"), '
2773 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002774 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002775 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002776 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002777
Tao Baodd2a5892015-03-12 12:32:37 -07002778 # Abort the OTA update. Note that the incremental OTA cannot be applied
2779 # even if it may match the checksum of the target partition.
2780 # a) If version < 3, operations like move and erase will make changes
2781 # unconditionally and damage the partition.
2782 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002783 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002784 if partition == "system":
2785 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2786 else:
2787 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2788 script.AppendExtra((
2789 'abort("E%d: %s partition has unexpected contents");\n'
2790 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002791
Yifan Hong10c530d2018-12-27 17:34:18 -08002792 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002793 partition = self.partition
2794 script.Print('Verifying the updated %s image...' % (partition,))
2795 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2796 ranges = self.tgt.care_map
2797 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002798 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002799 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002800 self.device, ranges_str,
2801 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002802
2803 # Bug: 20881595
2804 # Verify that extended blocks are really zeroed out.
2805 if self.tgt.extended:
2806 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002807 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002808 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002809 self.device, ranges_str,
2810 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002811 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002812 if partition == "system":
2813 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2814 else:
2815 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002816 script.AppendExtra(
2817 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002818 ' abort("E%d: %s partition has unexpected non-zero contents after '
2819 'OTA update");\n'
2820 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002821 else:
2822 script.Print('Verified the updated %s image.' % (partition,))
2823
Tianjie Xu209db462016-05-24 17:34:52 -07002824 if partition == "system":
2825 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2826 else:
2827 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2828
Tao Bao5fcaaef2015-06-01 13:40:49 -07002829 script.AppendExtra(
2830 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002831 ' abort("E%d: %s partition has unexpected contents after OTA '
2832 'update");\n'
2833 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002834
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002835 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002836 ZipWrite(output_zip,
2837 '{}.transfer.list'.format(self.path),
2838 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002839
Tao Bao76def242017-11-21 09:25:31 -08002840 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2841 # its size. Quailty 9 almost triples the compression time but doesn't
2842 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002843 # zip | brotli(quality 6) | brotli(quality 9)
2844 # compressed_size: 942M | 869M (~8% reduced) | 854M
2845 # compression_time: 75s | 265s | 719s
2846 # decompression_time: 15s | 25s | 25s
2847
2848 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002849 brotli_cmd = ['brotli', '--quality=6',
2850 '--output={}.new.dat.br'.format(self.path),
2851 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002852 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002853 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002854
2855 new_data_name = '{}.new.dat.br'.format(self.partition)
2856 ZipWrite(output_zip,
2857 '{}.new.dat.br'.format(self.path),
2858 new_data_name,
2859 compress_type=zipfile.ZIP_STORED)
2860 else:
2861 new_data_name = '{}.new.dat'.format(self.partition)
2862 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2863
Dan Albert8e0178d2015-01-27 15:53:15 -08002864 ZipWrite(output_zip,
2865 '{}.patch.dat'.format(self.path),
2866 '{}.patch.dat'.format(self.partition),
2867 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002868
Tianjie Xu209db462016-05-24 17:34:52 -07002869 if self.partition == "system":
2870 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2871 else:
2872 code = ErrorCode.VENDOR_UPDATE_FAILURE
2873
Yifan Hong10c530d2018-12-27 17:34:18 -08002874 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002875 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002876 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002877 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002878 device=self.device, partition=self.partition,
2879 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002880 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002881
Dan Albert8b72aef2015-03-23 19:13:21 -07002882 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002883 data = source.ReadRangeSet(ranges)
2884 ctx = sha1()
2885
2886 for p in data:
2887 ctx.update(p)
2888
2889 return ctx.hexdigest()
2890
Tao Baoe9b61912015-07-09 17:37:49 -07002891 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2892 """Return the hash value for all zero blocks."""
2893 zero_block = '\x00' * 4096
2894 ctx = sha1()
2895 for _ in range(num_blocks):
2896 ctx.update(zero_block)
2897
2898 return ctx.hexdigest()
2899
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002900
Tianjie Xu41976c72019-07-03 13:57:01 -07002901# Expose these two classes to support vendor-specific scripts
2902DataImage = images.DataImage
2903EmptyImage = images.EmptyImage
2904
Tao Bao76def242017-11-21 09:25:31 -08002905
Doug Zongker96a57e72010-09-26 14:57:41 -07002906# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002907PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002908 "ext4": "EMMC",
2909 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002910 "f2fs": "EMMC",
2911 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002912}
Doug Zongker96a57e72010-09-26 14:57:41 -07002913
Tao Bao76def242017-11-21 09:25:31 -08002914
Doug Zongker96a57e72010-09-26 14:57:41 -07002915def GetTypeAndDevice(mount_point, info):
2916 fstab = info["fstab"]
2917 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002918 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2919 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002920 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002921 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002922
2923
2924def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002925 """Parses and converts a PEM-encoded certificate into DER-encoded.
2926
2927 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2928
2929 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002930 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002931 """
2932 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002933 save = False
2934 for line in data.split("\n"):
2935 if "--END CERTIFICATE--" in line:
2936 break
2937 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002938 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002939 if "--BEGIN CERTIFICATE--" in line:
2940 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002941 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002942 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002943
Tao Bao04e1f012018-02-04 12:13:35 -08002944
2945def ExtractPublicKey(cert):
2946 """Extracts the public key (PEM-encoded) from the given certificate file.
2947
2948 Args:
2949 cert: The certificate filename.
2950
2951 Returns:
2952 The public key string.
2953
2954 Raises:
2955 AssertionError: On non-zero return from 'openssl'.
2956 """
2957 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2958 # While openssl 1.1 writes the key into the given filename followed by '-out',
2959 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2960 # stdout instead.
2961 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2962 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2963 pubkey, stderrdata = proc.communicate()
2964 assert proc.returncode == 0, \
2965 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2966 return pubkey
2967
2968
Tao Bao1ac886e2019-06-26 11:58:22 -07002969def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002970 """Extracts the AVB public key from the given public or private key.
2971
2972 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002973 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002974 key: The input key file, which should be PEM-encoded public or private key.
2975
2976 Returns:
2977 The path to the extracted AVB public key file.
2978 """
2979 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2980 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002981 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002982 return output
2983
2984
Doug Zongker412c02f2014-02-13 10:58:24 -08002985def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2986 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002987 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002988
Tao Bao6d5d6232018-03-09 17:04:42 -08002989 Most of the space in the boot and recovery images is just the kernel, which is
2990 identical for the two, so the resulting patch should be efficient. Add it to
2991 the output zip, along with a shell script that is run from init.rc on first
2992 boot to actually do the patching and install the new recovery image.
2993
2994 Args:
2995 input_dir: The top-level input directory of the target-files.zip.
2996 output_sink: The callback function that writes the result.
2997 recovery_img: File object for the recovery image.
2998 boot_img: File objects for the boot image.
2999 info_dict: A dict returned by common.LoadInfoDict() on the input
3000 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003001 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003002 if info_dict is None:
3003 info_dict = OPTIONS.info_dict
3004
Tao Bao6d5d6232018-03-09 17:04:42 -08003005 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003006 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3007
3008 if board_uses_vendorimage:
3009 # In this case, the output sink is rooted at VENDOR
3010 recovery_img_path = "etc/recovery.img"
3011 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3012 sh_dir = "bin"
3013 else:
3014 # In this case the output sink is rooted at SYSTEM
3015 recovery_img_path = "vendor/etc/recovery.img"
3016 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3017 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003018
Tao Baof2cffbd2015-07-22 12:33:18 -07003019 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003020 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003021
3022 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003023 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003024 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003025 # With system-root-image, boot and recovery images will have mismatching
3026 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3027 # to handle such a case.
3028 if system_root_image:
3029 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003030 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003031 assert not os.path.exists(path)
3032 else:
3033 diff_program = ["imgdiff"]
3034 if os.path.exists(path):
3035 diff_program.append("-b")
3036 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003037 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003038 else:
3039 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003040
3041 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3042 _, _, patch = d.ComputePatch()
3043 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003044
Dan Albertebb19aa2015-03-27 19:11:53 -07003045 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003046 # The following GetTypeAndDevice()s need to use the path in the target
3047 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07003048 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
3049 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
3050 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003051 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003052
Tao Baof2cffbd2015-07-22 12:33:18 -07003053 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003054
3055 # Note that we use /vendor to refer to the recovery resources. This will
3056 # work for a separate vendor partition mounted at /vendor or a
3057 # /system/vendor subdirectory on the system partition, for which init will
3058 # create a symlink from /vendor to /system/vendor.
3059
3060 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003061if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3062 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003063 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003064 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3065 log -t recovery "Installing new recovery image: succeeded" || \\
3066 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003067else
3068 log -t recovery "Recovery image already installed"
3069fi
3070""" % {'type': recovery_type,
3071 'device': recovery_device,
3072 'sha1': recovery_img.sha1,
3073 'size': recovery_img.size}
3074 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003075 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003076if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3077 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003078 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003079 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3080 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3081 log -t recovery "Installing new recovery image: succeeded" || \\
3082 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003083else
3084 log -t recovery "Recovery image already installed"
3085fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003086""" % {'boot_size': boot_img.size,
3087 'boot_sha1': boot_img.sha1,
3088 'recovery_size': recovery_img.size,
3089 'recovery_sha1': recovery_img.sha1,
3090 'boot_type': boot_type,
3091 'boot_device': boot_device,
3092 'recovery_type': recovery_type,
3093 'recovery_device': recovery_device,
3094 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003095
Bill Peckhame868aec2019-09-17 17:06:47 -07003096 # The install script location moved from /system/etc to /system/bin in the L
3097 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3098 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003099
Tao Bao32fcdab2018-10-12 10:30:39 -07003100 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003101
Tao Baoda30cfa2017-12-01 16:19:46 -08003102 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003103
3104
3105class DynamicPartitionUpdate(object):
3106 def __init__(self, src_group=None, tgt_group=None, progress=None,
3107 block_difference=None):
3108 self.src_group = src_group
3109 self.tgt_group = tgt_group
3110 self.progress = progress
3111 self.block_difference = block_difference
3112
3113 @property
3114 def src_size(self):
3115 if not self.block_difference:
3116 return 0
3117 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3118
3119 @property
3120 def tgt_size(self):
3121 if not self.block_difference:
3122 return 0
3123 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3124
3125 @staticmethod
3126 def _GetSparseImageSize(img):
3127 if not img:
3128 return 0
3129 return img.blocksize * img.total_blocks
3130
3131
3132class DynamicGroupUpdate(object):
3133 def __init__(self, src_size=None, tgt_size=None):
3134 # None: group does not exist. 0: no size limits.
3135 self.src_size = src_size
3136 self.tgt_size = tgt_size
3137
3138
3139class DynamicPartitionsDifference(object):
3140 def __init__(self, info_dict, block_diffs, progress_dict=None,
3141 source_info_dict=None):
3142 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003143 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003144
3145 self._remove_all_before_apply = False
3146 if source_info_dict is None:
3147 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003148 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003149
Tao Baof1113e92019-06-18 12:10:14 -07003150 block_diff_dict = collections.OrderedDict(
3151 [(e.partition, e) for e in block_diffs])
3152
Yifan Hong10c530d2018-12-27 17:34:18 -08003153 assert len(block_diff_dict) == len(block_diffs), \
3154 "Duplicated BlockDifference object for {}".format(
3155 [partition for partition, count in
3156 collections.Counter(e.partition for e in block_diffs).items()
3157 if count > 1])
3158
Yifan Hong79997e52019-01-23 16:56:19 -08003159 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003160
3161 for p, block_diff in block_diff_dict.items():
3162 self._partition_updates[p] = DynamicPartitionUpdate()
3163 self._partition_updates[p].block_difference = block_diff
3164
3165 for p, progress in progress_dict.items():
3166 if p in self._partition_updates:
3167 self._partition_updates[p].progress = progress
3168
3169 tgt_groups = shlex.split(info_dict.get(
3170 "super_partition_groups", "").strip())
3171 src_groups = shlex.split(source_info_dict.get(
3172 "super_partition_groups", "").strip())
3173
3174 for g in tgt_groups:
3175 for p in shlex.split(info_dict.get(
3176 "super_%s_partition_list" % g, "").strip()):
3177 assert p in self._partition_updates, \
3178 "{} is in target super_{}_partition_list but no BlockDifference " \
3179 "object is provided.".format(p, g)
3180 self._partition_updates[p].tgt_group = g
3181
3182 for g in src_groups:
3183 for p in shlex.split(source_info_dict.get(
3184 "super_%s_partition_list" % g, "").strip()):
3185 assert p in self._partition_updates, \
3186 "{} is in source super_{}_partition_list but no BlockDifference " \
3187 "object is provided.".format(p, g)
3188 self._partition_updates[p].src_group = g
3189
Yifan Hong45433e42019-01-18 13:55:25 -08003190 target_dynamic_partitions = set(shlex.split(info_dict.get(
3191 "dynamic_partition_list", "").strip()))
3192 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3193 if u.tgt_size)
3194 assert block_diffs_with_target == target_dynamic_partitions, \
3195 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3196 list(target_dynamic_partitions), list(block_diffs_with_target))
3197
3198 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3199 "dynamic_partition_list", "").strip()))
3200 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3201 if u.src_size)
3202 assert block_diffs_with_source == source_dynamic_partitions, \
3203 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3204 list(source_dynamic_partitions), list(block_diffs_with_source))
3205
Yifan Hong10c530d2018-12-27 17:34:18 -08003206 if self._partition_updates:
3207 logger.info("Updating dynamic partitions %s",
3208 self._partition_updates.keys())
3209
Yifan Hong79997e52019-01-23 16:56:19 -08003210 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003211
3212 for g in tgt_groups:
3213 self._group_updates[g] = DynamicGroupUpdate()
3214 self._group_updates[g].tgt_size = int(info_dict.get(
3215 "super_%s_group_size" % g, "0").strip())
3216
3217 for g in src_groups:
3218 if g not in self._group_updates:
3219 self._group_updates[g] = DynamicGroupUpdate()
3220 self._group_updates[g].src_size = int(source_info_dict.get(
3221 "super_%s_group_size" % g, "0").strip())
3222
3223 self._Compute()
3224
3225 def WriteScript(self, script, output_zip, write_verify_script=False):
3226 script.Comment('--- Start patching dynamic partitions ---')
3227 for p, u in self._partition_updates.items():
3228 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3229 script.Comment('Patch partition %s' % p)
3230 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3231 write_verify_script=False)
3232
3233 op_list_path = MakeTempFile()
3234 with open(op_list_path, 'w') as f:
3235 for line in self._op_list:
3236 f.write('{}\n'.format(line))
3237
3238 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3239
3240 script.Comment('Update dynamic partition metadata')
3241 script.AppendExtra('assert(update_dynamic_partitions('
3242 'package_extract_file("dynamic_partitions_op_list")));')
3243
3244 if write_verify_script:
3245 for p, u in self._partition_updates.items():
3246 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3247 u.block_difference.WritePostInstallVerifyScript(script)
3248 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3249
3250 for p, u in self._partition_updates.items():
3251 if u.tgt_size and u.src_size <= u.tgt_size:
3252 script.Comment('Patch partition %s' % p)
3253 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3254 write_verify_script=write_verify_script)
3255 if write_verify_script:
3256 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3257
3258 script.Comment('--- End patching dynamic partitions ---')
3259
3260 def _Compute(self):
3261 self._op_list = list()
3262
3263 def append(line):
3264 self._op_list.append(line)
3265
3266 def comment(line):
3267 self._op_list.append("# %s" % line)
3268
3269 if self._remove_all_before_apply:
3270 comment('Remove all existing dynamic partitions and groups before '
3271 'applying full OTA')
3272 append('remove_all_groups')
3273
3274 for p, u in self._partition_updates.items():
3275 if u.src_group and not u.tgt_group:
3276 append('remove %s' % p)
3277
3278 for p, u in self._partition_updates.items():
3279 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3280 comment('Move partition %s from %s to default' % (p, u.src_group))
3281 append('move %s default' % p)
3282
3283 for p, u in self._partition_updates.items():
3284 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3285 comment('Shrink partition %s from %d to %d' %
3286 (p, u.src_size, u.tgt_size))
3287 append('resize %s %s' % (p, u.tgt_size))
3288
3289 for g, u in self._group_updates.items():
3290 if u.src_size is not None and u.tgt_size is None:
3291 append('remove_group %s' % g)
3292 if (u.src_size is not None and u.tgt_size is not None and
3293 u.src_size > u.tgt_size):
3294 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3295 append('resize_group %s %d' % (g, u.tgt_size))
3296
3297 for g, u in self._group_updates.items():
3298 if u.src_size is None and u.tgt_size is not None:
3299 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3300 append('add_group %s %d' % (g, u.tgt_size))
3301 if (u.src_size is not None and u.tgt_size is not None and
3302 u.src_size < u.tgt_size):
3303 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3304 append('resize_group %s %d' % (g, u.tgt_size))
3305
3306 for p, u in self._partition_updates.items():
3307 if u.tgt_group and not u.src_group:
3308 comment('Add partition %s to group %s' % (p, u.tgt_group))
3309 append('add %s %s' % (p, u.tgt_group))
3310
3311 for p, u in self._partition_updates.items():
3312 if u.tgt_size and u.src_size < u.tgt_size:
3313 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3314 append('resize %s %d' % (p, u.tgt_size))
3315
3316 for p, u in self._partition_updates.items():
3317 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3318 comment('Move partition %s from default to %s' %
3319 (p, u.tgt_group))
3320 append('move %s %s' % (p, u.tgt_group))