blob: 8a59a39f1f494514772eb3978ea846c5e767603b [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()
Hongguang Chenb4702b72020-05-13 18:05:20 -0700811 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3) :
Tianjie Xu9afb2212020-05-10 21:48:15 +0000812 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700813
814 if len(tokens) == 3:
815 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
816 return {}
817
Tianjie Xu9afb2212020-05-10 21:48:15 +0000818 import_path = tokens[1]
819 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
820 raise ValueError('Unrecognized import path {}'.format(line))
821
822 # We only recognize a subset of import statement that the init process
823 # supports. And we can loose the restriction based on how the dynamic
824 # fingerprint is used in practice. The placeholder format should be
825 # ${placeholder}, and its value should be provided by the caller through
826 # the placeholder_values.
827 for prop, value in self.placeholder_values.items():
828 prop_place_holder = '${{{}}}'.format(prop)
829 if prop_place_holder in import_path:
830 import_path = import_path.replace(prop_place_holder, value)
831 if '$' in import_path:
832 logger.info('Unresolved place holder in import path %s', import_path)
833 return {}
834
835 import_path = import_path.replace('/{}'.format(self.partition),
836 self.partition.upper())
837 logger.info('Parsing build props override from %s', import_path)
838
839 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
840 d = LoadDictionaryFromLines(lines)
841 return {key: val for key, val in d.items()
842 if key in self.props_allow_override}
843
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000844 def GetProp(self, prop):
845 return self.build_props.get(prop)
846
847
Tianjie Xucfa86222016-03-07 16:31:19 -0800848def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
849 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700850 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700851 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700852 self.mount_point = mount_point
853 self.fs_type = fs_type
854 self.device = device
855 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700856 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700857 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700858
859 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800860 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700861 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700862 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700863 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700864
Tao Baod1de6f32017-03-01 16:38:48 -0800865 assert fstab_version == 2
866
867 d = {}
868 for line in data.split("\n"):
869 line = line.strip()
870 if not line or line.startswith("#"):
871 continue
872
873 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
874 pieces = line.split()
875 if len(pieces) != 5:
876 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
877
878 # Ignore entries that are managed by vold.
879 options = pieces[4]
880 if "voldmanaged=" in options:
881 continue
882
883 # It's a good line, parse it.
884 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700885 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800886 options = options.split(",")
887 for i in options:
888 if i.startswith("length="):
889 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700890 elif i == "slotselect":
891 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800892 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800893 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700894 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800895
Tao Baod1de6f32017-03-01 16:38:48 -0800896 mount_flags = pieces[3]
897 # Honor the SELinux context if present.
898 context = None
899 for i in mount_flags.split(","):
900 if i.startswith("context="):
901 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800902
Tao Baod1de6f32017-03-01 16:38:48 -0800903 mount_point = pieces[1]
904 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700905 device=pieces[0], length=length, context=context,
906 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800907
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700908 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700909 # system. Other areas assume system is always at "/system" so point /system
910 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700911 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800912 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700913 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700914 return d
915
916
Tao Bao765668f2019-10-04 22:03:00 -0700917def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
918 """Finds the path to recovery fstab and loads its contents."""
919 # recovery fstab is only meaningful when installing an update via recovery
920 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -0700921 if info_dict.get('ab_update') == 'true' and \
922 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -0700923 return None
924
925 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
926 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
927 # cases, since it may load the info_dict from an old build (e.g. when
928 # generating incremental OTAs from that build).
929 system_root_image = info_dict.get('system_root_image') == 'true'
930 if info_dict.get('no_recovery') != 'true':
931 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
932 if isinstance(input_file, zipfile.ZipFile):
933 if recovery_fstab_path not in input_file.namelist():
934 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
935 else:
936 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
937 if not os.path.exists(path):
938 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
939 return LoadRecoveryFSTab(
940 read_helper, info_dict['fstab_version'], recovery_fstab_path,
941 system_root_image)
942
943 if info_dict.get('recovery_as_boot') == 'true':
944 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
945 if isinstance(input_file, zipfile.ZipFile):
946 if recovery_fstab_path not in input_file.namelist():
947 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
948 else:
949 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
950 if not os.path.exists(path):
951 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
952 return LoadRecoveryFSTab(
953 read_helper, info_dict['fstab_version'], recovery_fstab_path,
954 system_root_image)
955
956 return None
957
958
Doug Zongker37974732010-09-16 17:44:38 -0700959def DumpInfoDict(d):
960 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700961 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700962
Dan Albert8b72aef2015-03-23 19:13:21 -0700963
Daniel Norman55417142019-11-25 16:04:36 -0800964def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700965 """Merges dynamic partition info variables.
966
967 Args:
968 framework_dict: The dictionary of dynamic partition info variables from the
969 partial framework target files.
970 vendor_dict: The dictionary of dynamic partition info variables from the
971 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700972
973 Returns:
974 The merged dynamic partition info dictionary.
975 """
976 merged_dict = {}
977 # Partition groups and group sizes are defined by the vendor dict because
978 # these values may vary for each board that uses a shared system image.
979 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800980 framework_dynamic_partition_list = framework_dict.get(
981 "dynamic_partition_list", "")
982 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
983 merged_dict["dynamic_partition_list"] = ("%s %s" % (
984 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700985 for partition_group in merged_dict["super_partition_groups"].split(" "):
986 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800987 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700988 if key not in vendor_dict:
989 raise ValueError("Vendor dict does not contain required key %s." % key)
990 merged_dict[key] = vendor_dict[key]
991
992 # Set the partition group's partition list using a concatenation of the
993 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800994 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700995 merged_dict[key] = (
996 "%s %s" %
997 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530998
999 # Pick virtual ab related flags from vendor dict, if defined.
1000 if "virtual_ab" in vendor_dict.keys():
1001 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
1002 if "virtual_ab_retrofit" in vendor_dict.keys():
1003 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001004 return merged_dict
1005
1006
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001007def AppendAVBSigningArgs(cmd, partition):
1008 """Append signing arguments for avbtool."""
1009 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1010 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001011 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1012 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1013 if os.path.exists(new_key_path):
1014 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001015 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1016 if key_path and algorithm:
1017 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001018 avb_salt = OPTIONS.info_dict.get("avb_salt")
1019 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001020 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001021 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001022
1023
Tao Bao765668f2019-10-04 22:03:00 -07001024def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001025 """Returns the VBMeta arguments for partition.
1026
1027 It sets up the VBMeta argument by including the partition descriptor from the
1028 given 'image', or by configuring the partition as a chained partition.
1029
1030 Args:
1031 partition: The name of the partition (e.g. "system").
1032 image: The path to the partition image.
1033 info_dict: A dict returned by common.LoadInfoDict(). Will use
1034 OPTIONS.info_dict if None has been given.
1035
1036 Returns:
1037 A list of VBMeta arguments.
1038 """
1039 if info_dict is None:
1040 info_dict = OPTIONS.info_dict
1041
1042 # Check if chain partition is used.
1043 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001044 if not key_path:
1045 return ["--include_descriptors_from_image", image]
1046
1047 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1048 # into vbmeta.img. The recovery image will be configured on an independent
1049 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1050 # See details at
1051 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001052 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001053 return []
1054
1055 # Otherwise chain the partition into vbmeta.
1056 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1057 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001058
1059
Tao Bao02a08592018-07-22 12:40:45 -07001060def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1061 """Constructs and returns the arg to build or verify a chained partition.
1062
1063 Args:
1064 partition: The partition name.
1065 info_dict: The info dict to look up the key info and rollback index
1066 location.
1067 key: The key to be used for building or verifying the partition. Defaults to
1068 the key listed in info_dict.
1069
1070 Returns:
1071 A string of form "partition:rollback_index_location:key" that can be used to
1072 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001073 """
1074 if key is None:
1075 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001076 if key and not os.path.exists(key) and OPTIONS.search_path:
1077 new_key_path = os.path.join(OPTIONS.search_path, key)
1078 if os.path.exists(new_key_path):
1079 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001080 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001081 rollback_index_location = info_dict[
1082 "avb_" + partition + "_rollback_index_location"]
1083 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1084
1085
Tianjie20dd8f22020-04-19 15:51:16 -07001086def ConstructAftlMakeImageCommands(output_image):
1087 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001088
1089 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001090 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001091 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1092 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1093 'No AFTL manufacturer key provided.'
1094
1095 vbmeta_image = MakeTempFile()
1096 os.rename(output_image, vbmeta_image)
1097 build_info = BuildInfo(OPTIONS.info_dict)
1098 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001099 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001100 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001101 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001102 "--vbmeta_image_path", vbmeta_image,
1103 "--output", output_image,
1104 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001105 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001106 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1107 "--algorithm", "SHA256_RSA4096",
1108 "--padding", "4096"]
1109 if OPTIONS.aftl_signer_helper:
1110 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001111 return aftl_cmd
1112
1113
1114def AddAftlInclusionProof(output_image):
1115 """Appends the aftl inclusion proof to the vbmeta image."""
1116
1117 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001118 RunAndCheckOutput(aftl_cmd)
1119
1120 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1121 output_image, '--transparency_log_pub_keys',
1122 OPTIONS.aftl_key_path]
1123 RunAndCheckOutput(verify_cmd)
1124
1125
Daniel Norman276f0622019-07-26 14:13:51 -07001126def BuildVBMeta(image_path, partitions, name, needed_partitions):
1127 """Creates a VBMeta image.
1128
1129 It generates the requested VBMeta image. The requested image could be for
1130 top-level or chained VBMeta image, which is determined based on the name.
1131
1132 Args:
1133 image_path: The output path for the new VBMeta image.
1134 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001135 values. Only valid partition names are accepted, as partitions listed
1136 in common.AVB_PARTITIONS and custom partitions listed in
1137 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001138 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1139 needed_partitions: Partitions whose descriptors should be included into the
1140 generated VBMeta image.
1141
1142 Raises:
1143 AssertionError: On invalid input args.
1144 """
1145 avbtool = OPTIONS.info_dict["avb_avbtool"]
1146 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1147 AppendAVBSigningArgs(cmd, name)
1148
Hongguang Chenf23364d2020-04-27 18:36:36 -07001149 custom_partitions = OPTIONS.info_dict.get(
1150 "avb_custom_images_partition_list", "").strip().split()
1151
Daniel Norman276f0622019-07-26 14:13:51 -07001152 for partition, path in partitions.items():
1153 if partition not in needed_partitions:
1154 continue
1155 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001156 partition in AVB_VBMETA_PARTITIONS or
1157 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001158 'Unknown partition: {}'.format(partition)
1159 assert os.path.exists(path), \
1160 'Failed to find {} for {}'.format(path, partition)
1161 cmd.extend(GetAvbPartitionArg(partition, path))
1162
1163 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1164 if args and args.strip():
1165 split_args = shlex.split(args)
1166 for index, arg in enumerate(split_args[:-1]):
1167 # Sanity check that the image file exists. Some images might be defined
1168 # as a path relative to source tree, which may not be available at the
1169 # same location when running this script (we have the input target_files
1170 # zip only). For such cases, we additionally scan other locations (e.g.
1171 # IMAGES/, RADIO/, etc) before bailing out.
1172 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001173 chained_image = split_args[index + 1]
1174 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001175 continue
1176 found = False
1177 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1178 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001179 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001180 if os.path.exists(alt_path):
1181 split_args[index + 1] = alt_path
1182 found = True
1183 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001184 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001185 cmd.extend(split_args)
1186
1187 RunAndCheckOutput(cmd)
1188
Tianjie Xueaed60c2020-03-12 00:33:28 -07001189 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001190 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001191 AddAftlInclusionProof(image_path)
1192
Daniel Norman276f0622019-07-26 14:13:51 -07001193
J. Avila98cd4cc2020-06-10 20:09:10 +00001194def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001195 ramdisk_img = tempfile.NamedTemporaryFile()
1196
1197 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1198 cmd = ["mkbootfs", "-f", fs_config_file,
1199 os.path.join(sourcedir, "RAMDISK")]
1200 else:
1201 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1202 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001203 if lz4_ramdisks:
1204 p2 = Run(["lz4", "-l", "-12" , "--favor-decSpeed"], stdin=p1.stdout,
1205 stdout=ramdisk_img.file.fileno())
1206 else:
1207 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001208
1209 p2.wait()
1210 p1.wait()
1211 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001212 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001213
1214 return ramdisk_img
1215
1216
Steve Muckle9793cf62020-04-08 18:27:00 -07001217def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001218 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001219 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001220
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001221 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001222 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1223 we are building a two-step special image (i.e. building a recovery image to
1224 be loaded into /boot in two-step OTAs).
1225
1226 Return the image data, or None if sourcedir does not appear to contains files
1227 for building the requested image.
1228 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001229
Steve Muckle9793cf62020-04-08 18:27:00 -07001230 # "boot" or "recovery", without extension.
1231 partition_name = os.path.basename(sourcedir).lower()
1232
1233 if partition_name == "recovery":
1234 kernel = "kernel"
1235 else:
1236 kernel = image_name.replace("boot", "kernel")
1237 kernel = kernel.replace(".img","")
1238 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001239 return None
1240
1241 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001242 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001243
Doug Zongkerd5131602012-08-02 14:46:42 -07001244 if info_dict is None:
1245 info_dict = OPTIONS.info_dict
1246
Doug Zongkereef39442009-04-02 12:14:19 -07001247 img = tempfile.NamedTemporaryFile()
1248
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001249 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001250 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1251 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001252
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001253 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1254 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1255
Steve Muckle9793cf62020-04-08 18:27:00 -07001256 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001257
Benoit Fradina45a8682014-07-14 21:00:43 +02001258 fn = os.path.join(sourcedir, "second")
1259 if os.access(fn, os.F_OK):
1260 cmd.append("--second")
1261 cmd.append(fn)
1262
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001263 fn = os.path.join(sourcedir, "dtb")
1264 if os.access(fn, os.F_OK):
1265 cmd.append("--dtb")
1266 cmd.append(fn)
1267
Doug Zongker171f1cd2009-06-15 22:36:37 -07001268 fn = os.path.join(sourcedir, "cmdline")
1269 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001270 cmd.append("--cmdline")
1271 cmd.append(open(fn).read().rstrip("\n"))
1272
1273 fn = os.path.join(sourcedir, "base")
1274 if os.access(fn, os.F_OK):
1275 cmd.append("--base")
1276 cmd.append(open(fn).read().rstrip("\n"))
1277
Ying Wang4de6b5b2010-08-25 14:29:34 -07001278 fn = os.path.join(sourcedir, "pagesize")
1279 if os.access(fn, os.F_OK):
1280 cmd.append("--pagesize")
1281 cmd.append(open(fn).read().rstrip("\n"))
1282
Steve Mucklef84668e2020-03-16 19:13:46 -07001283 if partition_name == "recovery":
1284 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301285 if not args:
1286 # Fall back to "mkbootimg_args" for recovery image
1287 # in case "recovery_mkbootimg_args" is not set.
1288 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001289 else:
1290 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001291 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001292 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001293
Tao Bao76def242017-11-21 09:25:31 -08001294 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001295 if args and args.strip():
1296 cmd.extend(shlex.split(args))
1297
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001298 if has_ramdisk:
1299 cmd.extend(["--ramdisk", ramdisk_img.name])
1300
Tao Baod95e9fd2015-03-29 23:07:41 -07001301 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001302 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001303 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001304 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001305 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001306 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001307
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001308 if partition_name == "recovery":
1309 if info_dict.get("include_recovery_dtbo") == "true":
1310 fn = os.path.join(sourcedir, "recovery_dtbo")
1311 cmd.extend(["--recovery_dtbo", fn])
1312 if info_dict.get("include_recovery_acpio") == "true":
1313 fn = os.path.join(sourcedir, "recovery_acpio")
1314 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001315
Tao Bao986ee862018-10-04 15:46:16 -07001316 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001317
Tao Bao76def242017-11-21 09:25:31 -08001318 if (info_dict.get("boot_signer") == "true" and
1319 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001320 # Hard-code the path as "/boot" for two-step special recovery image (which
1321 # will be loaded into /boot during the two-step OTA).
1322 if two_step_image:
1323 path = "/boot"
1324 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001325 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001326 cmd = [OPTIONS.boot_signer_path]
1327 cmd.extend(OPTIONS.boot_signer_args)
1328 cmd.extend([path, img.name,
1329 info_dict["verity_key"] + ".pk8",
1330 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001331 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001332
Tao Baod95e9fd2015-03-29 23:07:41 -07001333 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001334 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001335 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001336 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001337 # We have switched from the prebuilt futility binary to using the tool
1338 # (futility-host) built from the source. Override the setting in the old
1339 # TF.zip.
1340 futility = info_dict["futility"]
1341 if futility.startswith("prebuilts/"):
1342 futility = "futility-host"
1343 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001344 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001345 info_dict["vboot_key"] + ".vbprivk",
1346 info_dict["vboot_subkey"] + ".vbprivk",
1347 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001348 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001349 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001350
Tao Baof3282b42015-04-01 11:21:55 -07001351 # Clean up the temp files.
1352 img_unsigned.close()
1353 img_keyblock.close()
1354
David Zeuthen8fecb282017-12-01 16:24:01 -05001355 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001356 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001357 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001358 if partition_name == "recovery":
1359 part_size = info_dict["recovery_size"]
1360 else:
1361 part_size = info_dict[image_name.replace(".img","_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001362 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001363 "--partition_size", str(part_size), "--partition_name",
1364 partition_name]
1365 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001366 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001367 if args and args.strip():
1368 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001369 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001370
1371 img.seek(os.SEEK_SET, 0)
1372 data = img.read()
1373
1374 if has_ramdisk:
1375 ramdisk_img.close()
1376 img.close()
1377
1378 return data
1379
1380
Doug Zongkerd5131602012-08-02 14:46:42 -07001381def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001382 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001383 """Return a File object with the desired bootable image.
1384
1385 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1386 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1387 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001388
Doug Zongker55d93282011-01-25 17:03:34 -08001389 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1390 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001391 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001392 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001393
1394 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1395 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001396 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001397 return File.FromLocalFile(name, prebuilt_path)
1398
Tao Bao32fcdab2018-10-12 10:30:39 -07001399 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001400
1401 if info_dict is None:
1402 info_dict = OPTIONS.info_dict
1403
1404 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001405 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1406 # for recovery.
1407 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1408 prebuilt_name != "boot.img" or
1409 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001410
Doug Zongker6f1d0312014-08-22 08:07:12 -07001411 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001412 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001413 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001414 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001415 if data:
1416 return File(name, data)
1417 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001418
Doug Zongkereef39442009-04-02 12:14:19 -07001419
Steve Mucklee1b10862019-07-10 10:49:37 -07001420def _BuildVendorBootImage(sourcedir, info_dict=None):
1421 """Build a vendor boot image from the specified sourcedir.
1422
1423 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1424 turn them into a vendor boot image.
1425
1426 Return the image data, or None if sourcedir does not appear to contains files
1427 for building the requested image.
1428 """
1429
1430 if info_dict is None:
1431 info_dict = OPTIONS.info_dict
1432
1433 img = tempfile.NamedTemporaryFile()
1434
J. Avila98cd4cc2020-06-10 20:09:10 +00001435 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1436 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001437
1438 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1439 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1440
1441 cmd = [mkbootimg]
1442
1443 fn = os.path.join(sourcedir, "dtb")
1444 if os.access(fn, os.F_OK):
1445 cmd.append("--dtb")
1446 cmd.append(fn)
1447
1448 fn = os.path.join(sourcedir, "vendor_cmdline")
1449 if os.access(fn, os.F_OK):
1450 cmd.append("--vendor_cmdline")
1451 cmd.append(open(fn).read().rstrip("\n"))
1452
1453 fn = os.path.join(sourcedir, "base")
1454 if os.access(fn, os.F_OK):
1455 cmd.append("--base")
1456 cmd.append(open(fn).read().rstrip("\n"))
1457
1458 fn = os.path.join(sourcedir, "pagesize")
1459 if os.access(fn, os.F_OK):
1460 cmd.append("--pagesize")
1461 cmd.append(open(fn).read().rstrip("\n"))
1462
1463 args = info_dict.get("mkbootimg_args")
1464 if args and args.strip():
1465 cmd.extend(shlex.split(args))
1466
1467 args = info_dict.get("mkbootimg_version_args")
1468 if args and args.strip():
1469 cmd.extend(shlex.split(args))
1470
1471 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1472 cmd.extend(["--vendor_boot", img.name])
1473
1474 RunAndCheckOutput(cmd)
1475
1476 # AVB: if enabled, calculate and add hash.
1477 if info_dict.get("avb_enable") == "true":
1478 avbtool = info_dict["avb_avbtool"]
1479 part_size = info_dict["vendor_boot_size"]
1480 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001481 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001482 AppendAVBSigningArgs(cmd, "vendor_boot")
1483 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1484 if args and args.strip():
1485 cmd.extend(shlex.split(args))
1486 RunAndCheckOutput(cmd)
1487
1488 img.seek(os.SEEK_SET, 0)
1489 data = img.read()
1490
1491 ramdisk_img.close()
1492 img.close()
1493
1494 return data
1495
1496
1497def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1498 info_dict=None):
1499 """Return a File object with the desired vendor boot image.
1500
1501 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1502 the source files in 'unpack_dir'/'tree_subdir'."""
1503
1504 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1505 if os.path.exists(prebuilt_path):
1506 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1507 return File.FromLocalFile(name, prebuilt_path)
1508
1509 logger.info("building image from target_files %s...", tree_subdir)
1510
1511 if info_dict is None:
1512 info_dict = OPTIONS.info_dict
1513
1514 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1515 if data:
1516 return File(name, data)
1517 return None
1518
1519
Narayan Kamatha07bf042017-08-14 14:49:21 +01001520def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001521 """Gunzips the given gzip compressed file to a given output file."""
1522 with gzip.open(in_filename, "rb") as in_file, \
1523 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001524 shutil.copyfileobj(in_file, out_file)
1525
1526
Tao Bao0ff15de2019-03-20 11:26:06 -07001527def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001528 """Unzips the archive to the given directory.
1529
1530 Args:
1531 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001532 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001533 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1534 archvie. Non-matching patterns will be filtered out. If there's no match
1535 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001536 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001537 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001538 if patterns is not None:
1539 # Filter out non-matching patterns. unzip will complain otherwise.
1540 with zipfile.ZipFile(filename) as input_zip:
1541 names = input_zip.namelist()
1542 filtered = [
1543 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1544
1545 # There isn't any matching files. Don't unzip anything.
1546 if not filtered:
1547 return
1548 cmd.extend(filtered)
1549
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001550 RunAndCheckOutput(cmd)
1551
1552
Doug Zongker75f17362009-12-08 13:46:44 -08001553def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001554 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001555
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001556 Args:
1557 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1558 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1559
1560 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1561 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001562
Tao Bao1c830bf2017-12-25 10:43:47 -08001563 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001564 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001565 """
Doug Zongkereef39442009-04-02 12:14:19 -07001566
Tao Bao1c830bf2017-12-25 10:43:47 -08001567 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001568 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1569 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001570 UnzipToDir(m.group(1), tmp, pattern)
1571 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001572 filename = m.group(1)
1573 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001574 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001575
Tao Baodba59ee2018-01-09 13:21:02 -08001576 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001577
1578
Yifan Hong8a66a712019-04-04 15:37:57 -07001579def GetUserImage(which, tmpdir, input_zip,
1580 info_dict=None,
1581 allow_shared_blocks=None,
1582 hashtree_info_generator=None,
1583 reset_file_map=False):
1584 """Returns an Image object suitable for passing to BlockImageDiff.
1585
1586 This function loads the specified image from the given path. If the specified
1587 image is sparse, it also performs additional processing for OTA purpose. For
1588 example, it always adds block 0 to clobbered blocks list. It also detects
1589 files that cannot be reconstructed from the block list, for whom we should
1590 avoid applying imgdiff.
1591
1592 Args:
1593 which: The partition name.
1594 tmpdir: The directory that contains the prebuilt image and block map file.
1595 input_zip: The target-files ZIP archive.
1596 info_dict: The dict to be looked up for relevant info.
1597 allow_shared_blocks: If image is sparse, whether having shared blocks is
1598 allowed. If none, it is looked up from info_dict.
1599 hashtree_info_generator: If present and image is sparse, generates the
1600 hashtree_info for this sparse image.
1601 reset_file_map: If true and image is sparse, reset file map before returning
1602 the image.
1603 Returns:
1604 A Image object. If it is a sparse image and reset_file_map is False, the
1605 image will have file_map info loaded.
1606 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001607 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001608 info_dict = LoadInfoDict(input_zip)
1609
1610 is_sparse = info_dict.get("extfs_sparse_flag")
1611
1612 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1613 # shared blocks (i.e. some blocks will show up in multiple files' block
1614 # list). We can only allocate such shared blocks to the first "owner", and
1615 # disable imgdiff for all later occurrences.
1616 if allow_shared_blocks is None:
1617 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1618
1619 if is_sparse:
1620 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1621 hashtree_info_generator)
1622 if reset_file_map:
1623 img.ResetFileMap()
1624 return img
1625 else:
1626 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1627
1628
1629def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1630 """Returns a Image object suitable for passing to BlockImageDiff.
1631
1632 This function loads the specified non-sparse image from the given path.
1633
1634 Args:
1635 which: The partition name.
1636 tmpdir: The directory that contains the prebuilt image and block map file.
1637 Returns:
1638 A Image object.
1639 """
1640 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1641 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1642
1643 # The image and map files must have been created prior to calling
1644 # ota_from_target_files.py (since LMP).
1645 assert os.path.exists(path) and os.path.exists(mappath)
1646
Tianjie Xu41976c72019-07-03 13:57:01 -07001647 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1648
Yifan Hong8a66a712019-04-04 15:37:57 -07001649
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001650def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1651 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001652 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1653
1654 This function loads the specified sparse image from the given path, and
1655 performs additional processing for OTA purpose. For example, it always adds
1656 block 0 to clobbered blocks list. It also detects files that cannot be
1657 reconstructed from the block list, for whom we should avoid applying imgdiff.
1658
1659 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001660 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001661 tmpdir: The directory that contains the prebuilt image and block map file.
1662 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001663 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001664 hashtree_info_generator: If present, generates the hashtree_info for this
1665 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001666 Returns:
1667 A SparseImage object, with file_map info loaded.
1668 """
Tao Baoc765cca2018-01-31 17:32:40 -08001669 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1670 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1671
1672 # The image and map files must have been created prior to calling
1673 # ota_from_target_files.py (since LMP).
1674 assert os.path.exists(path) and os.path.exists(mappath)
1675
1676 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1677 # it to clobbered_blocks so that it will be written to the target
1678 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1679 clobbered_blocks = "0"
1680
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001681 image = sparse_img.SparseImage(
1682 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1683 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001684
1685 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1686 # if they contain all zeros. We can't reconstruct such a file from its block
1687 # list. Tag such entries accordingly. (Bug: 65213616)
1688 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001689 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001690 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001691 continue
1692
Tom Cherryd14b8952018-08-09 14:26:00 -07001693 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1694 # filename listed in system.map may contain an additional leading slash
1695 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1696 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001697 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001698
Tom Cherryd14b8952018-08-09 14:26:00 -07001699 # Special handling another case, where files not under /system
1700 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001701 if which == 'system' and not arcname.startswith('SYSTEM'):
1702 arcname = 'ROOT/' + arcname
1703
1704 assert arcname in input_zip.namelist(), \
1705 "Failed to find the ZIP entry for {}".format(entry)
1706
Tao Baoc765cca2018-01-31 17:32:40 -08001707 info = input_zip.getinfo(arcname)
1708 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001709
1710 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001711 # image, check the original block list to determine its completeness. Note
1712 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001713 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001714 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001715
Tao Baoc765cca2018-01-31 17:32:40 -08001716 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1717 ranges.extra['incomplete'] = True
1718
1719 return image
1720
1721
Doug Zongkereef39442009-04-02 12:14:19 -07001722def GetKeyPasswords(keylist):
1723 """Given a list of keys, prompt the user to enter passwords for
1724 those which require them. Return a {key: password} dict. password
1725 will be None if the key has no password."""
1726
Doug Zongker8ce7c252009-05-22 13:34:54 -07001727 no_passwords = []
1728 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001729 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001730 devnull = open("/dev/null", "w+b")
1731 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001732 # We don't need a password for things that aren't really keys.
1733 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001734 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001735 continue
1736
T.R. Fullhart37e10522013-03-18 10:31:26 -07001737 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001738 "-inform", "DER", "-nocrypt"],
1739 stdin=devnull.fileno(),
1740 stdout=devnull.fileno(),
1741 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001742 p.communicate()
1743 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001744 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001745 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001746 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001747 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1748 "-inform", "DER", "-passin", "pass:"],
1749 stdin=devnull.fileno(),
1750 stdout=devnull.fileno(),
1751 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001752 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001753 if p.returncode == 0:
1754 # Encrypted key with empty string as password.
1755 key_passwords[k] = ''
1756 elif stderr.startswith('Error decrypting key'):
1757 # Definitely encrypted key.
1758 # It would have said "Error reading key" if it didn't parse correctly.
1759 need_passwords.append(k)
1760 else:
1761 # Potentially, a type of key that openssl doesn't understand.
1762 # We'll let the routines in signapk.jar handle it.
1763 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001764 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001765
T.R. Fullhart37e10522013-03-18 10:31:26 -07001766 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001767 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001768 return key_passwords
1769
1770
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001771def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001772 """Gets the minSdkVersion declared in the APK.
1773
changho.shin0f125362019-07-08 10:59:00 +09001774 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001775 This can be both a decimal number (API Level) or a codename.
1776
1777 Args:
1778 apk_name: The APK filename.
1779
1780 Returns:
1781 The parsed SDK version string.
1782
1783 Raises:
1784 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001785 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001786 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001787 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001788 stderr=subprocess.PIPE)
1789 stdoutdata, stderrdata = proc.communicate()
1790 if proc.returncode != 0:
1791 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001792 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001793 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001794
Tao Baof47bf0f2018-03-21 23:28:51 -07001795 for line in stdoutdata.split("\n"):
1796 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001797 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1798 if m:
1799 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001800 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001801
1802
1803def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001804 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001805
Tao Baof47bf0f2018-03-21 23:28:51 -07001806 If minSdkVersion is set to a codename, it is translated to a number using the
1807 provided map.
1808
1809 Args:
1810 apk_name: The APK filename.
1811
1812 Returns:
1813 The parsed SDK version number.
1814
1815 Raises:
1816 ExternalError: On failing to get the min SDK version number.
1817 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001818 version = GetMinSdkVersion(apk_name)
1819 try:
1820 return int(version)
1821 except ValueError:
1822 # Not a decimal number. Codename?
1823 if version in codename_to_api_level_map:
1824 return codename_to_api_level_map[version]
1825 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001826 raise ExternalError(
1827 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1828 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001829
1830
1831def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001832 codename_to_api_level_map=None, whole_file=False,
1833 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001834 """Sign the input_name zip/jar/apk, producing output_name. Use the
1835 given key and password (the latter may be None if the key does not
1836 have a password.
1837
Doug Zongker951495f2009-08-14 12:44:19 -07001838 If whole_file is true, use the "-w" option to SignApk to embed a
1839 signature that covers the whole file in the archive comment of the
1840 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001841
1842 min_api_level is the API Level (int) of the oldest platform this file may end
1843 up on. If not specified for an APK, the API Level is obtained by interpreting
1844 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1845
1846 codename_to_api_level_map is needed to translate the codename which may be
1847 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001848
1849 Caller may optionally specify extra args to be passed to SignApk, which
1850 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001851 """
Tao Bao76def242017-11-21 09:25:31 -08001852 if codename_to_api_level_map is None:
1853 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001854 if extra_signapk_args is None:
1855 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001856
Alex Klyubin9667b182015-12-10 13:38:50 -08001857 java_library_path = os.path.join(
1858 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1859
Tao Baoe95540e2016-11-08 12:08:53 -08001860 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1861 ["-Djava.library.path=" + java_library_path,
1862 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001863 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001864 if whole_file:
1865 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001866
1867 min_sdk_version = min_api_level
1868 if min_sdk_version is None:
1869 if not whole_file:
1870 min_sdk_version = GetMinSdkVersionInt(
1871 input_name, codename_to_api_level_map)
1872 if min_sdk_version is not None:
1873 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1874
T.R. Fullhart37e10522013-03-18 10:31:26 -07001875 cmd.extend([key + OPTIONS.public_key_suffix,
1876 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001877 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001878
Tao Bao73dd4f42018-10-04 16:25:33 -07001879 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001880 if password is not None:
1881 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001882 stdoutdata, _ = proc.communicate(password)
1883 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001884 raise ExternalError(
1885 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001886 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001887
Doug Zongkereef39442009-04-02 12:14:19 -07001888
Doug Zongker37974732010-09-16 17:44:38 -07001889def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001890 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001891
Tao Bao9dd909e2017-11-14 11:27:32 -08001892 For non-AVB images, raise exception if the data is too big. Print a warning
1893 if the data is nearing the maximum size.
1894
1895 For AVB images, the actual image size should be identical to the limit.
1896
1897 Args:
1898 data: A string that contains all the data for the partition.
1899 target: The partition name. The ".img" suffix is optional.
1900 info_dict: The dict to be looked up for relevant info.
1901 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001902 if target.endswith(".img"):
1903 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001904 mount_point = "/" + target
1905
Ying Wangf8824af2014-06-03 14:07:27 -07001906 fs_type = None
1907 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001908 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001909 if mount_point == "/userdata":
1910 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001911 p = info_dict["fstab"][mount_point]
1912 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001913 device = p.device
1914 if "/" in device:
1915 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001916 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001917 if not fs_type or not limit:
1918 return
Doug Zongkereef39442009-04-02 12:14:19 -07001919
Andrew Boie0f9aec82012-02-14 09:32:52 -08001920 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001921 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1922 # path.
1923 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1924 if size != limit:
1925 raise ExternalError(
1926 "Mismatching image size for %s: expected %d actual %d" % (
1927 target, limit, size))
1928 else:
1929 pct = float(size) * 100.0 / limit
1930 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1931 if pct >= 99.0:
1932 raise ExternalError(msg)
1933 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001934 logger.warning("\n WARNING: %s\n", msg)
1935 else:
1936 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001937
1938
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001939def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001940 """Parses the APK certs info from a given target-files zip.
1941
1942 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1943 tuple with the following elements: (1) a dictionary that maps packages to
1944 certs (based on the "certificate" and "private_key" attributes in the file;
1945 (2) a string representing the extension of compressed APKs in the target files
1946 (e.g ".gz", ".bro").
1947
1948 Args:
1949 tf_zip: The input target_files ZipFile (already open).
1950
1951 Returns:
1952 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1953 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1954 no compressed APKs.
1955 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001956 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001957 compressed_extension = None
1958
Tao Bao0f990332017-09-08 19:02:54 -07001959 # META/apkcerts.txt contains the info for _all_ the packages known at build
1960 # time. Filter out the ones that are not installed.
1961 installed_files = set()
1962 for name in tf_zip.namelist():
1963 basename = os.path.basename(name)
1964 if basename:
1965 installed_files.add(basename)
1966
Tao Baoda30cfa2017-12-01 16:19:46 -08001967 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001968 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001969 if not line:
1970 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001971 m = re.match(
1972 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001973 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1974 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001975 line)
1976 if not m:
1977 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001978
Tao Bao818ddf52018-01-05 11:17:34 -08001979 matches = m.groupdict()
1980 cert = matches["CERT"]
1981 privkey = matches["PRIVKEY"]
1982 name = matches["NAME"]
1983 this_compressed_extension = matches["COMPRESSED"]
1984
1985 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1986 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1987 if cert in SPECIAL_CERT_STRINGS and not privkey:
1988 certmap[name] = cert
1989 elif (cert.endswith(OPTIONS.public_key_suffix) and
1990 privkey.endswith(OPTIONS.private_key_suffix) and
1991 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1992 certmap[name] = cert[:-public_key_suffix_len]
1993 else:
1994 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1995
1996 if not this_compressed_extension:
1997 continue
1998
1999 # Only count the installed files.
2000 filename = name + '.' + this_compressed_extension
2001 if filename not in installed_files:
2002 continue
2003
2004 # Make sure that all the values in the compression map have the same
2005 # extension. We don't support multiple compression methods in the same
2006 # system image.
2007 if compressed_extension:
2008 if this_compressed_extension != compressed_extension:
2009 raise ValueError(
2010 "Multiple compressed extensions: {} vs {}".format(
2011 compressed_extension, this_compressed_extension))
2012 else:
2013 compressed_extension = this_compressed_extension
2014
2015 return (certmap,
2016 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002017
2018
Doug Zongkereef39442009-04-02 12:14:19 -07002019COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002020Global options
2021
2022 -p (--path) <dir>
2023 Prepend <dir>/bin to the list of places to search for binaries run by this
2024 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002025
Doug Zongker05d3dea2009-06-22 11:32:31 -07002026 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002027 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002028
Tao Bao30df8b42018-04-23 15:32:53 -07002029 -x (--extra) <key=value>
2030 Add a key/value pair to the 'extras' dict, which device-specific extension
2031 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002032
Doug Zongkereef39442009-04-02 12:14:19 -07002033 -v (--verbose)
2034 Show command lines being executed.
2035
2036 -h (--help)
2037 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002038
2039 --logfile <file>
2040 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002041"""
2042
2043def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002044 print(docstring.rstrip("\n"))
2045 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002046
2047
2048def ParseOptions(argv,
2049 docstring,
2050 extra_opts="", extra_long_opts=(),
2051 extra_option_handler=None):
2052 """Parse the options in argv and return any arguments that aren't
2053 flags. docstring is the calling module's docstring, to be displayed
2054 for errors and -h. extra_opts and extra_long_opts are for flags
2055 defined by the caller, which are processed by passing them to
2056 extra_option_handler."""
2057
2058 try:
2059 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002060 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002061 ["help", "verbose", "path=", "signapk_path=",
2062 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002063 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002064 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2065 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002066 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2067 "aftl_key_path=", "aftl_manufacturer_key_path=",
2068 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002069 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002070 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002071 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002072 sys.exit(2)
2073
Doug Zongkereef39442009-04-02 12:14:19 -07002074 for o, a in opts:
2075 if o in ("-h", "--help"):
2076 Usage(docstring)
2077 sys.exit()
2078 elif o in ("-v", "--verbose"):
2079 OPTIONS.verbose = True
2080 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002081 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002082 elif o in ("--signapk_path",):
2083 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002084 elif o in ("--signapk_shared_library_path",):
2085 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002086 elif o in ("--extra_signapk_args",):
2087 OPTIONS.extra_signapk_args = shlex.split(a)
2088 elif o in ("--java_path",):
2089 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002090 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002091 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002092 elif o in ("--android_jar_path",):
2093 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002094 elif o in ("--public_key_suffix",):
2095 OPTIONS.public_key_suffix = a
2096 elif o in ("--private_key_suffix",):
2097 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002098 elif o in ("--boot_signer_path",):
2099 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002100 elif o in ("--boot_signer_args",):
2101 OPTIONS.boot_signer_args = shlex.split(a)
2102 elif o in ("--verity_signer_path",):
2103 OPTIONS.verity_signer_path = a
2104 elif o in ("--verity_signer_args",):
2105 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002106 elif o in ("--aftl_tool_path",):
2107 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002108 elif o in ("--aftl_server",):
2109 OPTIONS.aftl_server = a
2110 elif o in ("--aftl_key_path",):
2111 OPTIONS.aftl_key_path = a
2112 elif o in ("--aftl_manufacturer_key_path",):
2113 OPTIONS.aftl_manufacturer_key_path = a
2114 elif o in ("--aftl_signer_helper",):
2115 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002116 elif o in ("-s", "--device_specific"):
2117 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002118 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002119 key, value = a.split("=", 1)
2120 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002121 elif o in ("--logfile",):
2122 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002123 else:
2124 if extra_option_handler is None or not extra_option_handler(o, a):
2125 assert False, "unknown option \"%s\"" % (o,)
2126
Doug Zongker85448772014-09-09 14:59:20 -07002127 if OPTIONS.search_path:
2128 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2129 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002130
2131 return args
2132
2133
Tao Bao4c851b12016-09-19 13:54:38 -07002134def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002135 """Make a temp file and add it to the list of things to be deleted
2136 when Cleanup() is called. Return the filename."""
2137 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2138 os.close(fd)
2139 OPTIONS.tempfiles.append(fn)
2140 return fn
2141
2142
Tao Bao1c830bf2017-12-25 10:43:47 -08002143def MakeTempDir(prefix='tmp', suffix=''):
2144 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2145
2146 Returns:
2147 The absolute pathname of the new directory.
2148 """
2149 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2150 OPTIONS.tempfiles.append(dir_name)
2151 return dir_name
2152
2153
Doug Zongkereef39442009-04-02 12:14:19 -07002154def Cleanup():
2155 for i in OPTIONS.tempfiles:
2156 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002157 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002158 else:
2159 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002160 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002161
2162
2163class PasswordManager(object):
2164 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002165 self.editor = os.getenv("EDITOR")
2166 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002167
2168 def GetPasswords(self, items):
2169 """Get passwords corresponding to each string in 'items',
2170 returning a dict. (The dict may have keys in addition to the
2171 values in 'items'.)
2172
2173 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2174 user edit that file to add more needed passwords. If no editor is
2175 available, or $ANDROID_PW_FILE isn't define, prompts the user
2176 interactively in the ordinary way.
2177 """
2178
2179 current = self.ReadFile()
2180
2181 first = True
2182 while True:
2183 missing = []
2184 for i in items:
2185 if i not in current or not current[i]:
2186 missing.append(i)
2187 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002188 if not missing:
2189 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002190
2191 for i in missing:
2192 current[i] = ""
2193
2194 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002195 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002196 if sys.version_info[0] >= 3:
2197 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002198 answer = raw_input("try to edit again? [y]> ").strip()
2199 if answer and answer[0] not in 'yY':
2200 raise RuntimeError("key passwords unavailable")
2201 first = False
2202
2203 current = self.UpdateAndReadFile(current)
2204
Dan Albert8b72aef2015-03-23 19:13:21 -07002205 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002206 """Prompt the user to enter a value (password) for each key in
2207 'current' whose value is fales. Returns a new dict with all the
2208 values.
2209 """
2210 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002211 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002212 if v:
2213 result[k] = v
2214 else:
2215 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002216 result[k] = getpass.getpass(
2217 "Enter password for %s key> " % k).strip()
2218 if result[k]:
2219 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002220 return result
2221
2222 def UpdateAndReadFile(self, current):
2223 if not self.editor or not self.pwfile:
2224 return self.PromptResult(current)
2225
2226 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002227 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002228 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2229 f.write("# (Additional spaces are harmless.)\n\n")
2230
2231 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002232 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002233 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002234 f.write("[[[ %s ]]] %s\n" % (v, k))
2235 if not v and first_line is None:
2236 # position cursor on first line with no password.
2237 first_line = i + 4
2238 f.close()
2239
Tao Bao986ee862018-10-04 15:46:16 -07002240 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002241
2242 return self.ReadFile()
2243
2244 def ReadFile(self):
2245 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002246 if self.pwfile is None:
2247 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002248 try:
2249 f = open(self.pwfile, "r")
2250 for line in f:
2251 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002252 if not line or line[0] == '#':
2253 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002254 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2255 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002256 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002257 else:
2258 result[m.group(2)] = m.group(1)
2259 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002260 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002261 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002262 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002263 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002264
2265
Dan Albert8e0178d2015-01-27 15:53:15 -08002266def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2267 compress_type=None):
2268 import datetime
2269
2270 # http://b/18015246
2271 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2272 # for files larger than 2GiB. We can work around this by adjusting their
2273 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2274 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2275 # it isn't clear to me exactly what circumstances cause this).
2276 # `zipfile.write()` must be used directly to work around this.
2277 #
2278 # This mess can be avoided if we port to python3.
2279 saved_zip64_limit = zipfile.ZIP64_LIMIT
2280 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2281
2282 if compress_type is None:
2283 compress_type = zip_file.compression
2284 if arcname is None:
2285 arcname = filename
2286
2287 saved_stat = os.stat(filename)
2288
2289 try:
2290 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2291 # file to be zipped and reset it when we're done.
2292 os.chmod(filename, perms)
2293
2294 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002295 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2296 # intentional. zip stores datetimes in local time without a time zone
2297 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2298 # in the zip archive.
2299 local_epoch = datetime.datetime.fromtimestamp(0)
2300 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002301 os.utime(filename, (timestamp, timestamp))
2302
2303 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2304 finally:
2305 os.chmod(filename, saved_stat.st_mode)
2306 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2307 zipfile.ZIP64_LIMIT = saved_zip64_limit
2308
2309
Tao Bao58c1b962015-05-20 09:32:18 -07002310def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002311 compress_type=None):
2312 """Wrap zipfile.writestr() function to work around the zip64 limit.
2313
2314 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2315 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2316 when calling crc32(bytes).
2317
2318 But it still works fine to write a shorter string into a large zip file.
2319 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2320 when we know the string won't be too long.
2321 """
2322
2323 saved_zip64_limit = zipfile.ZIP64_LIMIT
2324 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2325
2326 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2327 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002328 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002329 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002330 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002331 else:
Tao Baof3282b42015-04-01 11:21:55 -07002332 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002333 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2334 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2335 # such a case (since
2336 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2337 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2338 # permission bits. We follow the logic in Python 3 to get consistent
2339 # behavior between using the two versions.
2340 if not zinfo.external_attr:
2341 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002342
2343 # If compress_type is given, it overrides the value in zinfo.
2344 if compress_type is not None:
2345 zinfo.compress_type = compress_type
2346
Tao Bao58c1b962015-05-20 09:32:18 -07002347 # If perms is given, it has a priority.
2348 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002349 # If perms doesn't set the file type, mark it as a regular file.
2350 if perms & 0o770000 == 0:
2351 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002352 zinfo.external_attr = perms << 16
2353
Tao Baof3282b42015-04-01 11:21:55 -07002354 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002355 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2356
Dan Albert8b72aef2015-03-23 19:13:21 -07002357 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002358 zipfile.ZIP64_LIMIT = saved_zip64_limit
2359
2360
Tao Bao89d7ab22017-12-14 17:05:33 -08002361def ZipDelete(zip_filename, entries):
2362 """Deletes entries from a ZIP file.
2363
2364 Since deleting entries from a ZIP file is not supported, it shells out to
2365 'zip -d'.
2366
2367 Args:
2368 zip_filename: The name of the ZIP file.
2369 entries: The name of the entry, or the list of names to be deleted.
2370
2371 Raises:
2372 AssertionError: In case of non-zero return from 'zip'.
2373 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002374 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002375 entries = [entries]
2376 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002377 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002378
2379
Tao Baof3282b42015-04-01 11:21:55 -07002380def ZipClose(zip_file):
2381 # http://b/18015246
2382 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2383 # central directory.
2384 saved_zip64_limit = zipfile.ZIP64_LIMIT
2385 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2386
2387 zip_file.close()
2388
2389 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002390
2391
2392class DeviceSpecificParams(object):
2393 module = None
2394 def __init__(self, **kwargs):
2395 """Keyword arguments to the constructor become attributes of this
2396 object, which is passed to all functions in the device-specific
2397 module."""
Tao Bao38884282019-07-10 22:20:56 -07002398 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002399 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002400 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002401
2402 if self.module is None:
2403 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002404 if not path:
2405 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002406 try:
2407 if os.path.isdir(path):
2408 info = imp.find_module("releasetools", [path])
2409 else:
2410 d, f = os.path.split(path)
2411 b, x = os.path.splitext(f)
2412 if x == ".py":
2413 f = b
2414 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002415 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002416 self.module = imp.load_module("device_specific", *info)
2417 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002418 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002419
2420 def _DoCall(self, function_name, *args, **kwargs):
2421 """Call the named function in the device-specific module, passing
2422 the given args and kwargs. The first argument to the call will be
2423 the DeviceSpecific object itself. If there is no module, or the
2424 module does not define the function, return the value of the
2425 'default' kwarg (which itself defaults to None)."""
2426 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002427 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002428 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2429
2430 def FullOTA_Assertions(self):
2431 """Called after emitting the block of assertions at the top of a
2432 full OTA package. Implementations can add whatever additional
2433 assertions they like."""
2434 return self._DoCall("FullOTA_Assertions")
2435
Doug Zongkere5ff5902012-01-17 10:55:37 -08002436 def FullOTA_InstallBegin(self):
2437 """Called at the start of full OTA installation."""
2438 return self._DoCall("FullOTA_InstallBegin")
2439
Yifan Hong10c530d2018-12-27 17:34:18 -08002440 def FullOTA_GetBlockDifferences(self):
2441 """Called during full OTA installation and verification.
2442 Implementation should return a list of BlockDifference objects describing
2443 the update on each additional partitions.
2444 """
2445 return self._DoCall("FullOTA_GetBlockDifferences")
2446
Doug Zongker05d3dea2009-06-22 11:32:31 -07002447 def FullOTA_InstallEnd(self):
2448 """Called at the end of full OTA installation; typically this is
2449 used to install the image for the device's baseband processor."""
2450 return self._DoCall("FullOTA_InstallEnd")
2451
2452 def IncrementalOTA_Assertions(self):
2453 """Called after emitting the block of assertions at the top of an
2454 incremental OTA package. Implementations can add whatever
2455 additional assertions they like."""
2456 return self._DoCall("IncrementalOTA_Assertions")
2457
Doug Zongkere5ff5902012-01-17 10:55:37 -08002458 def IncrementalOTA_VerifyBegin(self):
2459 """Called at the start of the verification phase of incremental
2460 OTA installation; additional checks can be placed here to abort
2461 the script before any changes are made."""
2462 return self._DoCall("IncrementalOTA_VerifyBegin")
2463
Doug Zongker05d3dea2009-06-22 11:32:31 -07002464 def IncrementalOTA_VerifyEnd(self):
2465 """Called at the end of the verification phase of incremental OTA
2466 installation; additional checks can be placed here to abort the
2467 script before any changes are made."""
2468 return self._DoCall("IncrementalOTA_VerifyEnd")
2469
Doug Zongkere5ff5902012-01-17 10:55:37 -08002470 def IncrementalOTA_InstallBegin(self):
2471 """Called at the start of incremental OTA installation (after
2472 verification is complete)."""
2473 return self._DoCall("IncrementalOTA_InstallBegin")
2474
Yifan Hong10c530d2018-12-27 17:34:18 -08002475 def IncrementalOTA_GetBlockDifferences(self):
2476 """Called during incremental OTA installation and verification.
2477 Implementation should return a list of BlockDifference objects describing
2478 the update on each additional partitions.
2479 """
2480 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2481
Doug Zongker05d3dea2009-06-22 11:32:31 -07002482 def IncrementalOTA_InstallEnd(self):
2483 """Called at the end of incremental OTA installation; typically
2484 this is used to install the image for the device's baseband
2485 processor."""
2486 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002487
Tao Bao9bc6bb22015-11-09 16:58:28 -08002488 def VerifyOTA_Assertions(self):
2489 return self._DoCall("VerifyOTA_Assertions")
2490
Tao Bao76def242017-11-21 09:25:31 -08002491
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002492class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002493 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002494 self.name = name
2495 self.data = data
2496 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002497 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002498 self.sha1 = sha1(data).hexdigest()
2499
2500 @classmethod
2501 def FromLocalFile(cls, name, diskname):
2502 f = open(diskname, "rb")
2503 data = f.read()
2504 f.close()
2505 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002506
2507 def WriteToTemp(self):
2508 t = tempfile.NamedTemporaryFile()
2509 t.write(self.data)
2510 t.flush()
2511 return t
2512
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002513 def WriteToDir(self, d):
2514 with open(os.path.join(d, self.name), "wb") as fp:
2515 fp.write(self.data)
2516
Geremy Condra36bd3652014-02-06 19:45:10 -08002517 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002518 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002519
Tao Bao76def242017-11-21 09:25:31 -08002520
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002521DIFF_PROGRAM_BY_EXT = {
2522 ".gz" : "imgdiff",
2523 ".zip" : ["imgdiff", "-z"],
2524 ".jar" : ["imgdiff", "-z"],
2525 ".apk" : ["imgdiff", "-z"],
2526 ".img" : "imgdiff",
2527 }
2528
Tao Bao76def242017-11-21 09:25:31 -08002529
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002530class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002531 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002532 self.tf = tf
2533 self.sf = sf
2534 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002535 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002536
2537 def ComputePatch(self):
2538 """Compute the patch (as a string of data) needed to turn sf into
2539 tf. Returns the same tuple as GetPatch()."""
2540
2541 tf = self.tf
2542 sf = self.sf
2543
Doug Zongker24cd2802012-08-14 16:36:15 -07002544 if self.diff_program:
2545 diff_program = self.diff_program
2546 else:
2547 ext = os.path.splitext(tf.name)[1]
2548 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002549
2550 ttemp = tf.WriteToTemp()
2551 stemp = sf.WriteToTemp()
2552
2553 ext = os.path.splitext(tf.name)[1]
2554
2555 try:
2556 ptemp = tempfile.NamedTemporaryFile()
2557 if isinstance(diff_program, list):
2558 cmd = copy.copy(diff_program)
2559 else:
2560 cmd = [diff_program]
2561 cmd.append(stemp.name)
2562 cmd.append(ttemp.name)
2563 cmd.append(ptemp.name)
2564 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002565 err = []
2566 def run():
2567 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002568 if e:
2569 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002570 th = threading.Thread(target=run)
2571 th.start()
2572 th.join(timeout=300) # 5 mins
2573 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002574 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002575 p.terminate()
2576 th.join(5)
2577 if th.is_alive():
2578 p.kill()
2579 th.join()
2580
Tianjie Xua2a9f992018-01-05 15:15:54 -08002581 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002582 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002583 self.patch = None
2584 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002585 diff = ptemp.read()
2586 finally:
2587 ptemp.close()
2588 stemp.close()
2589 ttemp.close()
2590
2591 self.patch = diff
2592 return self.tf, self.sf, self.patch
2593
2594
2595 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002596 """Returns a tuple of (target_file, source_file, patch_data).
2597
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002598 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002599 computing the patch failed.
2600 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002601 return self.tf, self.sf, self.patch
2602
2603
2604def ComputeDifferences(diffs):
2605 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002606 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002607
2608 # Do the largest files first, to try and reduce the long-pole effect.
2609 by_size = [(i.tf.size, i) for i in diffs]
2610 by_size.sort(reverse=True)
2611 by_size = [i[1] for i in by_size]
2612
2613 lock = threading.Lock()
2614 diff_iter = iter(by_size) # accessed under lock
2615
2616 def worker():
2617 try:
2618 lock.acquire()
2619 for d in diff_iter:
2620 lock.release()
2621 start = time.time()
2622 d.ComputePatch()
2623 dur = time.time() - start
2624 lock.acquire()
2625
2626 tf, sf, patch = d.GetPatch()
2627 if sf.name == tf.name:
2628 name = tf.name
2629 else:
2630 name = "%s (%s)" % (tf.name, sf.name)
2631 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002632 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002633 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002634 logger.info(
2635 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2636 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002637 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002638 except Exception:
2639 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002640 raise
2641
2642 # start worker threads; wait for them all to finish.
2643 threads = [threading.Thread(target=worker)
2644 for i in range(OPTIONS.worker_threads)]
2645 for th in threads:
2646 th.start()
2647 while threads:
2648 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002649
2650
Dan Albert8b72aef2015-03-23 19:13:21 -07002651class BlockDifference(object):
2652 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002653 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002654 self.tgt = tgt
2655 self.src = src
2656 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002657 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002658 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002659
Tao Baodd2a5892015-03-12 12:32:37 -07002660 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002661 version = max(
2662 int(i) for i in
2663 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002664 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002665 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002666
Tianjie Xu41976c72019-07-03 13:57:01 -07002667 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2668 version=self.version,
2669 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002670 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002671 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002672 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002673 self.touched_src_ranges = b.touched_src_ranges
2674 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002675
Yifan Hong10c530d2018-12-27 17:34:18 -08002676 # On devices with dynamic partitions, for new partitions,
2677 # src is None but OPTIONS.source_info_dict is not.
2678 if OPTIONS.source_info_dict is None:
2679 is_dynamic_build = OPTIONS.info_dict.get(
2680 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002681 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002682 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002683 is_dynamic_build = OPTIONS.source_info_dict.get(
2684 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002685 is_dynamic_source = partition in shlex.split(
2686 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002687
Yifan Hongbb2658d2019-01-25 12:30:58 -08002688 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002689 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2690
Yifan Hongbb2658d2019-01-25 12:30:58 -08002691 # For dynamic partitions builds, check partition list in both source
2692 # and target build because new partitions may be added, and existing
2693 # partitions may be removed.
2694 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2695
Yifan Hong10c530d2018-12-27 17:34:18 -08002696 if is_dynamic:
2697 self.device = 'map_partition("%s")' % partition
2698 else:
2699 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002700 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2701 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002702 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002703 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2704 OPTIONS.source_info_dict)
2705 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002706
Tao Baod8d14be2016-02-04 14:26:02 -08002707 @property
2708 def required_cache(self):
2709 return self._required_cache
2710
Tao Bao76def242017-11-21 09:25:31 -08002711 def WriteScript(self, script, output_zip, progress=None,
2712 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002713 if not self.src:
2714 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002715 script.Print("Patching %s image unconditionally..." % (self.partition,))
2716 else:
2717 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002718
Dan Albert8b72aef2015-03-23 19:13:21 -07002719 if progress:
2720 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002721 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002722
2723 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002724 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002725
Tao Bao9bc6bb22015-11-09 16:58:28 -08002726 def WriteStrictVerifyScript(self, script):
2727 """Verify all the blocks in the care_map, including clobbered blocks.
2728
2729 This differs from the WriteVerifyScript() function: a) it prints different
2730 error messages; b) it doesn't allow half-way updated images to pass the
2731 verification."""
2732
2733 partition = self.partition
2734 script.Print("Verifying %s..." % (partition,))
2735 ranges = self.tgt.care_map
2736 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002737 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002738 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2739 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002740 self.device, ranges_str,
2741 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002742 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002743 script.AppendExtra("")
2744
Tao Baod522bdc2016-04-12 15:53:16 -07002745 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002746 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002747
2748 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002749 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002750 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002751
2752 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002753 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002754 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002755 ranges = self.touched_src_ranges
2756 expected_sha1 = self.touched_src_sha1
2757 else:
2758 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2759 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002760
2761 # No blocks to be checked, skipping.
2762 if not ranges:
2763 return
2764
Tao Bao5ece99d2015-05-12 11:42:31 -07002765 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002766 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002767 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002768 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2769 '"%s.patch.dat")) then' % (
2770 self.device, ranges_str, expected_sha1,
2771 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002772 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002773 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002774
Tianjie Xufc3422a2015-12-15 11:53:59 -08002775 if self.version >= 4:
2776
2777 # Bug: 21124327
2778 # When generating incrementals for the system and vendor partitions in
2779 # version 4 or newer, explicitly check the first block (which contains
2780 # the superblock) of the partition to see if it's what we expect. If
2781 # this check fails, give an explicit log message about the partition
2782 # having been remounted R/W (the most likely explanation).
2783 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002784 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002785
2786 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002787 if partition == "system":
2788 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2789 else:
2790 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002791 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002792 'ifelse (block_image_recover({device}, "{ranges}") && '
2793 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002794 'package_extract_file("{partition}.transfer.list"), '
2795 '"{partition}.new.dat", "{partition}.patch.dat"), '
2796 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002797 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002798 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002799 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002800
Tao Baodd2a5892015-03-12 12:32:37 -07002801 # Abort the OTA update. Note that the incremental OTA cannot be applied
2802 # even if it may match the checksum of the target partition.
2803 # a) If version < 3, operations like move and erase will make changes
2804 # unconditionally and damage the partition.
2805 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002806 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002807 if partition == "system":
2808 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2809 else:
2810 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2811 script.AppendExtra((
2812 'abort("E%d: %s partition has unexpected contents");\n'
2813 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002814
Yifan Hong10c530d2018-12-27 17:34:18 -08002815 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002816 partition = self.partition
2817 script.Print('Verifying the updated %s image...' % (partition,))
2818 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2819 ranges = self.tgt.care_map
2820 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002821 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002822 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002823 self.device, ranges_str,
2824 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002825
2826 # Bug: 20881595
2827 # Verify that extended blocks are really zeroed out.
2828 if self.tgt.extended:
2829 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002830 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002831 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002832 self.device, ranges_str,
2833 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002834 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002835 if partition == "system":
2836 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2837 else:
2838 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002839 script.AppendExtra(
2840 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002841 ' abort("E%d: %s partition has unexpected non-zero contents after '
2842 'OTA update");\n'
2843 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002844 else:
2845 script.Print('Verified the updated %s image.' % (partition,))
2846
Tianjie Xu209db462016-05-24 17:34:52 -07002847 if partition == "system":
2848 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2849 else:
2850 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2851
Tao Bao5fcaaef2015-06-01 13:40:49 -07002852 script.AppendExtra(
2853 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002854 ' abort("E%d: %s partition has unexpected contents after OTA '
2855 'update");\n'
2856 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002857
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002858 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002859 ZipWrite(output_zip,
2860 '{}.transfer.list'.format(self.path),
2861 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002862
Tao Bao76def242017-11-21 09:25:31 -08002863 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2864 # its size. Quailty 9 almost triples the compression time but doesn't
2865 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002866 # zip | brotli(quality 6) | brotli(quality 9)
2867 # compressed_size: 942M | 869M (~8% reduced) | 854M
2868 # compression_time: 75s | 265s | 719s
2869 # decompression_time: 15s | 25s | 25s
2870
2871 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002872 brotli_cmd = ['brotli', '--quality=6',
2873 '--output={}.new.dat.br'.format(self.path),
2874 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002875 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002876 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002877
2878 new_data_name = '{}.new.dat.br'.format(self.partition)
2879 ZipWrite(output_zip,
2880 '{}.new.dat.br'.format(self.path),
2881 new_data_name,
2882 compress_type=zipfile.ZIP_STORED)
2883 else:
2884 new_data_name = '{}.new.dat'.format(self.partition)
2885 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2886
Dan Albert8e0178d2015-01-27 15:53:15 -08002887 ZipWrite(output_zip,
2888 '{}.patch.dat'.format(self.path),
2889 '{}.patch.dat'.format(self.partition),
2890 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002891
Tianjie Xu209db462016-05-24 17:34:52 -07002892 if self.partition == "system":
2893 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2894 else:
2895 code = ErrorCode.VENDOR_UPDATE_FAILURE
2896
Yifan Hong10c530d2018-12-27 17:34:18 -08002897 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002898 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002899 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002900 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002901 device=self.device, partition=self.partition,
2902 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002903 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002904
Dan Albert8b72aef2015-03-23 19:13:21 -07002905 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002906 data = source.ReadRangeSet(ranges)
2907 ctx = sha1()
2908
2909 for p in data:
2910 ctx.update(p)
2911
2912 return ctx.hexdigest()
2913
Tao Baoe9b61912015-07-09 17:37:49 -07002914 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2915 """Return the hash value for all zero blocks."""
2916 zero_block = '\x00' * 4096
2917 ctx = sha1()
2918 for _ in range(num_blocks):
2919 ctx.update(zero_block)
2920
2921 return ctx.hexdigest()
2922
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002923
Tianjie Xu41976c72019-07-03 13:57:01 -07002924# Expose these two classes to support vendor-specific scripts
2925DataImage = images.DataImage
2926EmptyImage = images.EmptyImage
2927
Tao Bao76def242017-11-21 09:25:31 -08002928
Doug Zongker96a57e72010-09-26 14:57:41 -07002929# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002930PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002931 "ext4": "EMMC",
2932 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002933 "f2fs": "EMMC",
2934 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002935}
Doug Zongker96a57e72010-09-26 14:57:41 -07002936
Yifan Hongbdb32012020-05-07 12:38:53 -07002937def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2938 """
2939 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2940 backwards compatibility. It aborts if the fstab entry has slotselect option
2941 (unless check_no_slot is explicitly set to False).
2942 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002943 fstab = info["fstab"]
2944 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07002945 if check_no_slot:
2946 assert not fstab[mount_point].slotselect, \
2947 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002948 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2949 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002950 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002951 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002952
2953
Yifan Hongbdb32012020-05-07 12:38:53 -07002954def GetTypeAndDeviceExpr(mount_point, info):
2955 """
2956 Return the filesystem of the partition, and an edify expression that evaluates
2957 to the device at runtime.
2958 """
2959 fstab = info["fstab"]
2960 if fstab:
2961 p = fstab[mount_point]
2962 device_expr = '"%s"' % fstab[mount_point].device
2963 if p.slotselect:
2964 device_expr = 'add_slot_suffix(%s)' % device_expr
2965 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
2966 else:
2967 raise KeyError
2968
2969
2970def GetEntryForDevice(fstab, device):
2971 """
2972 Returns:
2973 The first entry in fstab whose device is the given value.
2974 """
2975 if not fstab:
2976 return None
2977 for mount_point in fstab:
2978 if fstab[mount_point].device == device:
2979 return fstab[mount_point]
2980 return None
2981
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002982def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002983 """Parses and converts a PEM-encoded certificate into DER-encoded.
2984
2985 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2986
2987 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002988 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002989 """
2990 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002991 save = False
2992 for line in data.split("\n"):
2993 if "--END CERTIFICATE--" in line:
2994 break
2995 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002996 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002997 if "--BEGIN CERTIFICATE--" in line:
2998 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002999 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003000 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003001
Tao Bao04e1f012018-02-04 12:13:35 -08003002
3003def ExtractPublicKey(cert):
3004 """Extracts the public key (PEM-encoded) from the given certificate file.
3005
3006 Args:
3007 cert: The certificate filename.
3008
3009 Returns:
3010 The public key string.
3011
3012 Raises:
3013 AssertionError: On non-zero return from 'openssl'.
3014 """
3015 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3016 # While openssl 1.1 writes the key into the given filename followed by '-out',
3017 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3018 # stdout instead.
3019 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3020 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3021 pubkey, stderrdata = proc.communicate()
3022 assert proc.returncode == 0, \
3023 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3024 return pubkey
3025
3026
Tao Bao1ac886e2019-06-26 11:58:22 -07003027def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003028 """Extracts the AVB public key from the given public or private key.
3029
3030 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003031 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003032 key: The input key file, which should be PEM-encoded public or private key.
3033
3034 Returns:
3035 The path to the extracted AVB public key file.
3036 """
3037 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3038 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003039 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003040 return output
3041
3042
Doug Zongker412c02f2014-02-13 10:58:24 -08003043def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3044 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003045 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003046
Tao Bao6d5d6232018-03-09 17:04:42 -08003047 Most of the space in the boot and recovery images is just the kernel, which is
3048 identical for the two, so the resulting patch should be efficient. Add it to
3049 the output zip, along with a shell script that is run from init.rc on first
3050 boot to actually do the patching and install the new recovery image.
3051
3052 Args:
3053 input_dir: The top-level input directory of the target-files.zip.
3054 output_sink: The callback function that writes the result.
3055 recovery_img: File object for the recovery image.
3056 boot_img: File objects for the boot image.
3057 info_dict: A dict returned by common.LoadInfoDict() on the input
3058 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003059 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003060 if info_dict is None:
3061 info_dict = OPTIONS.info_dict
3062
Tao Bao6d5d6232018-03-09 17:04:42 -08003063 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003064 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3065
3066 if board_uses_vendorimage:
3067 # In this case, the output sink is rooted at VENDOR
3068 recovery_img_path = "etc/recovery.img"
3069 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3070 sh_dir = "bin"
3071 else:
3072 # In this case the output sink is rooted at SYSTEM
3073 recovery_img_path = "vendor/etc/recovery.img"
3074 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3075 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003076
Tao Baof2cffbd2015-07-22 12:33:18 -07003077 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003078 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003079
3080 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003081 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003082 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003083 # With system-root-image, boot and recovery images will have mismatching
3084 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3085 # to handle such a case.
3086 if system_root_image:
3087 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003088 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003089 assert not os.path.exists(path)
3090 else:
3091 diff_program = ["imgdiff"]
3092 if os.path.exists(path):
3093 diff_program.append("-b")
3094 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003095 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003096 else:
3097 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003098
3099 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3100 _, _, patch = d.ComputePatch()
3101 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003102
Dan Albertebb19aa2015-03-27 19:11:53 -07003103 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003104 # The following GetTypeAndDevice()s need to use the path in the target
3105 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003106 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3107 check_no_slot=False)
3108 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3109 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003110 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003111 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003112
Tao Baof2cffbd2015-07-22 12:33:18 -07003113 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003114
3115 # Note that we use /vendor to refer to the recovery resources. This will
3116 # work for a separate vendor partition mounted at /vendor or a
3117 # /system/vendor subdirectory on the system partition, for which init will
3118 # create a symlink from /vendor to /system/vendor.
3119
3120 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003121if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3122 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003123 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003124 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3125 log -t recovery "Installing new recovery image: succeeded" || \\
3126 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003127else
3128 log -t recovery "Recovery image already installed"
3129fi
3130""" % {'type': recovery_type,
3131 'device': recovery_device,
3132 'sha1': recovery_img.sha1,
3133 'size': recovery_img.size}
3134 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003135 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003136if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3137 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003138 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003139 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3140 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3141 log -t recovery "Installing new recovery image: succeeded" || \\
3142 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003143else
3144 log -t recovery "Recovery image already installed"
3145fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003146""" % {'boot_size': boot_img.size,
3147 'boot_sha1': boot_img.sha1,
3148 'recovery_size': recovery_img.size,
3149 'recovery_sha1': recovery_img.sha1,
3150 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003151 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
3152 'recovery_type': recovery_type + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003153 'recovery_device': recovery_device,
3154 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003155
Bill Peckhame868aec2019-09-17 17:06:47 -07003156 # The install script location moved from /system/etc to /system/bin in the L
3157 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3158 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003159
Tao Bao32fcdab2018-10-12 10:30:39 -07003160 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003161
Tao Baoda30cfa2017-12-01 16:19:46 -08003162 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003163
3164
3165class DynamicPartitionUpdate(object):
3166 def __init__(self, src_group=None, tgt_group=None, progress=None,
3167 block_difference=None):
3168 self.src_group = src_group
3169 self.tgt_group = tgt_group
3170 self.progress = progress
3171 self.block_difference = block_difference
3172
3173 @property
3174 def src_size(self):
3175 if not self.block_difference:
3176 return 0
3177 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3178
3179 @property
3180 def tgt_size(self):
3181 if not self.block_difference:
3182 return 0
3183 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3184
3185 @staticmethod
3186 def _GetSparseImageSize(img):
3187 if not img:
3188 return 0
3189 return img.blocksize * img.total_blocks
3190
3191
3192class DynamicGroupUpdate(object):
3193 def __init__(self, src_size=None, tgt_size=None):
3194 # None: group does not exist. 0: no size limits.
3195 self.src_size = src_size
3196 self.tgt_size = tgt_size
3197
3198
3199class DynamicPartitionsDifference(object):
3200 def __init__(self, info_dict, block_diffs, progress_dict=None,
3201 source_info_dict=None):
3202 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003203 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003204
3205 self._remove_all_before_apply = False
3206 if source_info_dict is None:
3207 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003208 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003209
Tao Baof1113e92019-06-18 12:10:14 -07003210 block_diff_dict = collections.OrderedDict(
3211 [(e.partition, e) for e in block_diffs])
3212
Yifan Hong10c530d2018-12-27 17:34:18 -08003213 assert len(block_diff_dict) == len(block_diffs), \
3214 "Duplicated BlockDifference object for {}".format(
3215 [partition for partition, count in
3216 collections.Counter(e.partition for e in block_diffs).items()
3217 if count > 1])
3218
Yifan Hong79997e52019-01-23 16:56:19 -08003219 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003220
3221 for p, block_diff in block_diff_dict.items():
3222 self._partition_updates[p] = DynamicPartitionUpdate()
3223 self._partition_updates[p].block_difference = block_diff
3224
3225 for p, progress in progress_dict.items():
3226 if p in self._partition_updates:
3227 self._partition_updates[p].progress = progress
3228
3229 tgt_groups = shlex.split(info_dict.get(
3230 "super_partition_groups", "").strip())
3231 src_groups = shlex.split(source_info_dict.get(
3232 "super_partition_groups", "").strip())
3233
3234 for g in tgt_groups:
3235 for p in shlex.split(info_dict.get(
3236 "super_%s_partition_list" % g, "").strip()):
3237 assert p in self._partition_updates, \
3238 "{} is in target super_{}_partition_list but no BlockDifference " \
3239 "object is provided.".format(p, g)
3240 self._partition_updates[p].tgt_group = g
3241
3242 for g in src_groups:
3243 for p in shlex.split(source_info_dict.get(
3244 "super_%s_partition_list" % g, "").strip()):
3245 assert p in self._partition_updates, \
3246 "{} is in source super_{}_partition_list but no BlockDifference " \
3247 "object is provided.".format(p, g)
3248 self._partition_updates[p].src_group = g
3249
Yifan Hong45433e42019-01-18 13:55:25 -08003250 target_dynamic_partitions = set(shlex.split(info_dict.get(
3251 "dynamic_partition_list", "").strip()))
3252 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3253 if u.tgt_size)
3254 assert block_diffs_with_target == target_dynamic_partitions, \
3255 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3256 list(target_dynamic_partitions), list(block_diffs_with_target))
3257
3258 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3259 "dynamic_partition_list", "").strip()))
3260 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3261 if u.src_size)
3262 assert block_diffs_with_source == source_dynamic_partitions, \
3263 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3264 list(source_dynamic_partitions), list(block_diffs_with_source))
3265
Yifan Hong10c530d2018-12-27 17:34:18 -08003266 if self._partition_updates:
3267 logger.info("Updating dynamic partitions %s",
3268 self._partition_updates.keys())
3269
Yifan Hong79997e52019-01-23 16:56:19 -08003270 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003271
3272 for g in tgt_groups:
3273 self._group_updates[g] = DynamicGroupUpdate()
3274 self._group_updates[g].tgt_size = int(info_dict.get(
3275 "super_%s_group_size" % g, "0").strip())
3276
3277 for g in src_groups:
3278 if g not in self._group_updates:
3279 self._group_updates[g] = DynamicGroupUpdate()
3280 self._group_updates[g].src_size = int(source_info_dict.get(
3281 "super_%s_group_size" % g, "0").strip())
3282
3283 self._Compute()
3284
3285 def WriteScript(self, script, output_zip, write_verify_script=False):
3286 script.Comment('--- Start patching dynamic partitions ---')
3287 for p, u in self._partition_updates.items():
3288 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3289 script.Comment('Patch partition %s' % p)
3290 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3291 write_verify_script=False)
3292
3293 op_list_path = MakeTempFile()
3294 with open(op_list_path, 'w') as f:
3295 for line in self._op_list:
3296 f.write('{}\n'.format(line))
3297
3298 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3299
3300 script.Comment('Update dynamic partition metadata')
3301 script.AppendExtra('assert(update_dynamic_partitions('
3302 'package_extract_file("dynamic_partitions_op_list")));')
3303
3304 if write_verify_script:
3305 for p, u in self._partition_updates.items():
3306 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3307 u.block_difference.WritePostInstallVerifyScript(script)
3308 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3309
3310 for p, u in self._partition_updates.items():
3311 if u.tgt_size and u.src_size <= u.tgt_size:
3312 script.Comment('Patch partition %s' % p)
3313 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3314 write_verify_script=write_verify_script)
3315 if write_verify_script:
3316 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3317
3318 script.Comment('--- End patching dynamic partitions ---')
3319
3320 def _Compute(self):
3321 self._op_list = list()
3322
3323 def append(line):
3324 self._op_list.append(line)
3325
3326 def comment(line):
3327 self._op_list.append("# %s" % line)
3328
3329 if self._remove_all_before_apply:
3330 comment('Remove all existing dynamic partitions and groups before '
3331 'applying full OTA')
3332 append('remove_all_groups')
3333
3334 for p, u in self._partition_updates.items():
3335 if u.src_group and not u.tgt_group:
3336 append('remove %s' % p)
3337
3338 for p, u in self._partition_updates.items():
3339 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3340 comment('Move partition %s from %s to default' % (p, u.src_group))
3341 append('move %s default' % p)
3342
3343 for p, u in self._partition_updates.items():
3344 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3345 comment('Shrink partition %s from %d to %d' %
3346 (p, u.src_size, u.tgt_size))
3347 append('resize %s %s' % (p, u.tgt_size))
3348
3349 for g, u in self._group_updates.items():
3350 if u.src_size is not None and u.tgt_size is None:
3351 append('remove_group %s' % g)
3352 if (u.src_size is not None and u.tgt_size is not None and
3353 u.src_size > u.tgt_size):
3354 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3355 append('resize_group %s %d' % (g, u.tgt_size))
3356
3357 for g, u in self._group_updates.items():
3358 if u.src_size is None and u.tgt_size is not None:
3359 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3360 append('add_group %s %d' % (g, u.tgt_size))
3361 if (u.src_size is not None and u.tgt_size is not None and
3362 u.src_size < u.tgt_size):
3363 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3364 append('resize_group %s %d' % (g, u.tgt_size))
3365
3366 for p, u in self._partition_updates.items():
3367 if u.tgt_group and not u.src_group:
3368 comment('Add partition %s to group %s' % (p, u.tgt_group))
3369 append('add %s %s' % (p, u.tgt_group))
3370
3371 for p, u in self._partition_updates.items():
3372 if u.tgt_size and u.src_size < u.tgt_size:
3373 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3374 append('resize %s %d' % (p, u.tgt_size))
3375
3376 for p, u in self._partition_updates.items():
3377 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3378 comment('Move partition %s from default to %s' %
3379 (p, u.tgt_group))
3380 append('move %s %s' % (p, u.tgt_group))