blob: 40db1a7447f975859a0b7c58ecd27c9f59a2776d [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 = []
Dan Austin52903642019-12-12 15:44:00 -080080 self.aftl_server = None
81 self.aftl_key_path = None
82 self.aftl_manufacturer_key_path = None
83 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070084 self.verbose = False
85 self.tempfiles = []
86 self.device_specific = None
87 self.extras = {}
88 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070089 self.source_info_dict = None
90 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070091 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070092 # Stash size cannot exceed cache_size * threshold.
93 self.cache_size = None
94 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070095 self.logfile = None
Dan Albert8b72aef2015-03-23 19:13:21 -070096
97
98OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070099
Tao Bao71197512018-10-11 14:08:45 -0700100# The block size that's used across the releasetools scripts.
101BLOCK_SIZE = 4096
102
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800103# Values for "certificate" in apkcerts that mean special things.
104SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
105
Tao Bao5cc0abb2019-03-21 10:18:05 -0700106# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
107# that system_other is not in the list because we don't want to include its
108# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900109AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Steve Mucklee1b10862019-07-10 10:49:37 -0700110 'system_ext', 'vendor', 'vendor_boot')
Tao Bao9dd909e2017-11-14 11:27:32 -0800111
Tao Bao08c190f2019-06-03 23:07:58 -0700112# Chained VBMeta partitions.
113AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
114
Tianjie Xu861f4132018-09-12 11:49:33 -0700115# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900116PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700117
118
Tianjie Xu209db462016-05-24 17:34:52 -0700119class ErrorCode(object):
120 """Define error_codes for failures that happen during the actual
121 update package installation.
122
123 Error codes 0-999 are reserved for failures before the package
124 installation (i.e. low battery, package verification failure).
125 Detailed code in 'bootable/recovery/error_code.h' """
126
127 SYSTEM_VERIFICATION_FAILURE = 1000
128 SYSTEM_UPDATE_FAILURE = 1001
129 SYSTEM_UNEXPECTED_CONTENTS = 1002
130 SYSTEM_NONZERO_CONTENTS = 1003
131 SYSTEM_RECOVER_FAILURE = 1004
132 VENDOR_VERIFICATION_FAILURE = 2000
133 VENDOR_UPDATE_FAILURE = 2001
134 VENDOR_UNEXPECTED_CONTENTS = 2002
135 VENDOR_NONZERO_CONTENTS = 2003
136 VENDOR_RECOVER_FAILURE = 2004
137 OEM_PROP_MISMATCH = 3000
138 FINGERPRINT_MISMATCH = 3001
139 THUMBPRINT_MISMATCH = 3002
140 OLDER_BUILD = 3003
141 DEVICE_MISMATCH = 3004
142 BAD_PATCH_FILE = 3005
143 INSUFFICIENT_CACHE_SPACE = 3006
144 TUNE_PARTITION_FAILURE = 3007
145 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800146
Tao Bao80921982018-03-21 21:02:19 -0700147
Dan Albert8b72aef2015-03-23 19:13:21 -0700148class ExternalError(RuntimeError):
149 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700150
151
Tao Bao32fcdab2018-10-12 10:30:39 -0700152def InitLogging():
153 DEFAULT_LOGGING_CONFIG = {
154 'version': 1,
155 'disable_existing_loggers': False,
156 'formatters': {
157 'standard': {
158 'format':
159 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
160 'datefmt': '%Y-%m-%d %H:%M:%S',
161 },
162 },
163 'handlers': {
164 'default': {
165 'class': 'logging.StreamHandler',
166 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700167 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700168 },
169 },
170 'loggers': {
171 '': {
172 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700173 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700174 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700175 }
176 }
177 }
178 env_config = os.getenv('LOGGING_CONFIG')
179 if env_config:
180 with open(env_config) as f:
181 config = json.load(f)
182 else:
183 config = DEFAULT_LOGGING_CONFIG
184
185 # Increase the logging level for verbose mode.
186 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700187 config = copy.deepcopy(config)
188 config['handlers']['default']['level'] = 'INFO'
189
190 if OPTIONS.logfile:
191 config = copy.deepcopy(config)
192 config['handlers']['logfile'] = {
193 'class': 'logging.FileHandler',
194 'formatter': 'standard',
195 'level': 'INFO',
196 'mode': 'w',
197 'filename': OPTIONS.logfile,
198 }
199 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700200
201 logging.config.dictConfig(config)
202
203
Tao Bao39451582017-05-04 11:10:47 -0700204def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700205 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700206
Tao Bao73dd4f42018-10-04 16:25:33 -0700207 Args:
208 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700209 verbose: Whether the commands should be shown. Default to the global
210 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700211 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
212 stdin, etc. stdout and stderr will default to subprocess.PIPE and
213 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800214 universal_newlines will default to True, as most of the users in
215 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700216
217 Returns:
218 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700219 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700220 if 'stdout' not in kwargs and 'stderr' not in kwargs:
221 kwargs['stdout'] = subprocess.PIPE
222 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800223 if 'universal_newlines' not in kwargs:
224 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700225 # Don't log any if caller explicitly says so.
226 if verbose != False:
227 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700228 return subprocess.Popen(args, **kwargs)
229
230
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800231def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800232 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800233
234 Args:
235 args: The command represented as a list of strings.
236 verbose: Whether the commands should be shown. Default to the global
237 verbosity if unspecified.
238 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
239 stdin, etc. stdout and stderr will default to subprocess.PIPE and
240 subprocess.STDOUT respectively unless caller specifies any of them.
241
Bill Peckham889b0c62019-02-21 18:53:37 -0800242 Raises:
243 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800244 """
245 proc = Run(args, verbose=verbose, **kwargs)
246 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800247
248 if proc.returncode != 0:
249 raise ExternalError(
250 "Failed to run command '{}' (exit code {})".format(
251 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800252
253
Tao Bao986ee862018-10-04 15:46:16 -0700254def RunAndCheckOutput(args, verbose=None, **kwargs):
255 """Runs the given command and returns the output.
256
257 Args:
258 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700259 verbose: Whether the commands should be shown. Default to the global
260 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700261 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
262 stdin, etc. stdout and stderr will default to subprocess.PIPE and
263 subprocess.STDOUT respectively unless caller specifies any of them.
264
265 Returns:
266 The output string.
267
268 Raises:
269 ExternalError: On non-zero exit from the command.
270 """
Tao Bao986ee862018-10-04 15:46:16 -0700271 proc = Run(args, verbose=verbose, **kwargs)
272 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800273 if output is None:
274 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700275 # Don't log any if caller explicitly says so.
276 if verbose != False:
277 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700278 if proc.returncode != 0:
279 raise ExternalError(
280 "Failed to run command '{}' (exit code {}):\n{}".format(
281 args, proc.returncode, output))
282 return output
283
284
Tao Baoc765cca2018-01-31 17:32:40 -0800285def RoundUpTo4K(value):
286 rounded_up = value + 4095
287 return rounded_up - (rounded_up % 4096)
288
289
Ying Wang7e6d4e42010-12-13 16:25:36 -0800290def CloseInheritedPipes():
291 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
292 before doing other work."""
293 if platform.system() != "Darwin":
294 return
295 for d in range(3, 1025):
296 try:
297 stat = os.fstat(d)
298 if stat is not None:
299 pipebit = stat[0] & 0x1000
300 if pipebit != 0:
301 os.close(d)
302 except OSError:
303 pass
304
305
Tao Bao1c320f82019-10-04 23:25:12 -0700306class BuildInfo(object):
307 """A class that holds the information for a given build.
308
309 This class wraps up the property querying for a given source or target build.
310 It abstracts away the logic of handling OEM-specific properties, and caches
311 the commonly used properties such as fingerprint.
312
313 There are two types of info dicts: a) build-time info dict, which is generated
314 at build time (i.e. included in a target_files zip); b) OEM info dict that is
315 specified at package generation time (via command line argument
316 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
317 having "oem_fingerprint_properties" in build-time info dict), all the queries
318 would be answered based on build-time info dict only. Otherwise if using
319 OEM-specific properties, some of them will be calculated from two info dicts.
320
321 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normanab5acef2020-01-08 17:01:11 -0800322 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700323
324 Attributes:
325 info_dict: The build-time info dict.
326 is_ab: Whether it's a build that uses A/B OTA.
327 oem_dicts: A list of OEM dicts.
328 oem_props: A list of OEM properties that should be read from OEM dicts; None
329 if the build doesn't use any OEM-specific property.
330 fingerprint: The fingerprint of the build, which would be calculated based
331 on OEM properties if applicable.
332 device: The device name, which could come from OEM dicts if applicable.
333 """
334
335 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
336 "ro.product.manufacturer", "ro.product.model",
337 "ro.product.name"]
Steven Laverdd33d752020-04-27 16:26:31 -0700338 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
339 "product", "odm", "vendor", "system_ext", "system"]
340 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
341 "product", "product_services", "odm", "vendor", "system"]
342 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700343
Tao Bao3ed35d32019-10-07 20:48:48 -0700344 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700345 """Initializes a BuildInfo instance with the given dicts.
346
347 Note that it only wraps up the given dicts, without making copies.
348
349 Arguments:
350 info_dict: The build-time info dict.
351 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
352 that it always uses the first dict to calculate the fingerprint or the
353 device name. The rest would be used for asserting OEM properties only
354 (e.g. one package can be installed on one of these devices).
355
356 Raises:
357 ValueError: On invalid inputs.
358 """
359 self.info_dict = info_dict
360 self.oem_dicts = oem_dicts
361
362 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700363
Hongguang Chen34354032020-05-03 21:24:26 -0700364 # Skip _oem_props if oem_dicts is None to use BuildInfo in
365 # sign_target_files_apks
366 if self.oem_dicts:
367 self._oem_props = info_dict.get("oem_fingerprint_properties")
368 else:
369 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700370
Daniel Normanab5acef2020-01-08 17:01:11 -0800371 def check_fingerprint(fingerprint):
372 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
373 raise ValueError(
374 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
375 "3.2.2. Build Parameters.".format(fingerprint))
376
377
378 self._partition_fingerprints = {}
379 for partition in PARTITIONS_WITH_CARE_MAP:
380 try:
381 fingerprint = self.CalculatePartitionFingerprint(partition)
382 check_fingerprint(fingerprint)
383 self._partition_fingerprints[partition] = fingerprint
384 except ExternalError:
385 continue
386 if "system" in self._partition_fingerprints:
387 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
388 # need a fingerprint when creating the image.
389 self._partition_fingerprints[
390 "system_other"] = self._partition_fingerprints["system"]
391
Tao Bao1c320f82019-10-04 23:25:12 -0700392 # These two should be computed only after setting self._oem_props.
Steve Kondik9d1c7eb2010-04-21 11:39:48 -0400393 self._device = info_dict.get("ota_override_device")
394 if not self._device:
395 self._device = self.GetOemProperty("ro.product.device")
Tao Bao1c320f82019-10-04 23:25:12 -0700396 self._fingerprint = self.CalculateFingerprint()
Daniel Normanab5acef2020-01-08 17:01:11 -0800397 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700398
399 @property
400 def is_ab(self):
401 return self._is_ab
402
403 @property
404 def device(self):
405 return self._device
406
407 @property
408 def fingerprint(self):
409 return self._fingerprint
410
411 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700412 def oem_props(self):
413 return self._oem_props
414
415 def __getitem__(self, key):
416 return self.info_dict[key]
417
418 def __setitem__(self, key, value):
419 self.info_dict[key] = value
420
421 def get(self, key, default=None):
422 return self.info_dict.get(key, default)
423
424 def items(self):
425 return self.info_dict.items()
426
Tianjie Xu726d20a2020-05-09 05:24:18 +0000427 def _GetRawBuildProp(self, prop, partition):
428 prop_file = '{}.build.prop'.format(
429 partition) if partition else 'build.prop'
430 partition_props = self.info_dict.get(prop_file)
431 if not partition_props:
432 return None
433 return partition_props.GetProp(prop)
434
Daniel Normanab5acef2020-01-08 17:01:11 -0800435 def GetPartitionBuildProp(self, prop, partition):
436 """Returns the inquired build property for the provided partition."""
437 # If provided a partition for this property, only look within that
438 # partition's build.prop.
439 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
440 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
441 else:
442 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu726d20a2020-05-09 05:24:18 +0000443
444 prop_val = self._GetRawBuildProp(prop, partition)
445 if prop_val is not None:
446 return prop_val
447 raise ExternalError("couldn't find %s in %s.build.prop" %
448 (prop, partition))
Daniel Normanab5acef2020-01-08 17:01:11 -0800449
Tao Bao1c320f82019-10-04 23:25:12 -0700450 def GetBuildProp(self, prop):
Daniel Normanab5acef2020-01-08 17:01:11 -0800451 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700452 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
453 return self._ResolveRoProductBuildProp(prop)
454
Tianjie Xu726d20a2020-05-09 05:24:18 +0000455 prop_val = self._GetRawBuildProp(prop, None)
456 if prop_val is not None:
457 return prop_val
458
459 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700460
461 def _ResolveRoProductBuildProp(self, prop):
462 """Resolves the inquired ro.product.* build property"""
Tianjie Xu726d20a2020-05-09 05:24:18 +0000463 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700464 if prop_val:
465 return prop_val
466
Steven Laverdd33d752020-04-27 16:26:31 -0700467 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu726d20a2020-05-09 05:24:18 +0000468 source_order_val = self._GetRawBuildProp(
469 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700470 if source_order_val:
471 source_order = source_order_val.split(",")
472 else:
Steven Laverdd33d752020-04-27 16:26:31 -0700473 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700474
475 # Check that all sources in ro.product.property_source_order are valid
Steven Laverdd33d752020-04-27 16:26:31 -0700476 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700477 raise ExternalError(
478 "Invalid ro.product.property_source_order '{}'".format(source_order))
479
Tianjie Xu726d20a2020-05-09 05:24:18 +0000480 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700481 source_prop = prop.replace(
Tianjie Xu726d20a2020-05-09 05:24:18 +0000482 "ro.product", "ro.product.{}".format(source_partition), 1)
483 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700484 if prop_val:
485 return prop_val
486
487 raise ExternalError("couldn't resolve {}".format(prop))
488
Steven Laverdd33d752020-04-27 16:26:31 -0700489 def _GetRoProductPropsDefaultSourceOrder(self):
490 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
491 # values of these properties for each Android release.
Tianjie Xu726d20a2020-05-09 05:24:18 +0000492 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laverdd33d752020-04-27 16:26:31 -0700493 if android_codename == "REL":
Tianjie Xu726d20a2020-05-09 05:24:18 +0000494 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laverdd33d752020-04-27 16:26:31 -0700495 if android_version == "10":
496 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
497 # NOTE: float() conversion of android_version will have rounding error.
498 # We are checking for "9" or less, and using "< 10" is well outside of
499 # possible floating point rounding.
500 try:
501 android_version_val = float(android_version)
502 except ValueError:
503 android_version_val = 0
504 if android_version_val < 10:
505 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
506 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
507
Tao Bao1c320f82019-10-04 23:25:12 -0700508 def GetOemProperty(self, key):
509 if self.oem_props is not None and key in self.oem_props:
510 return self.oem_dicts[0][key]
511 return self.GetBuildProp(key)
512
Daniel Normanab5acef2020-01-08 17:01:11 -0800513 def GetPartitionFingerprint(self, partition):
514 return self._partition_fingerprints.get(partition, None)
515
516 def CalculatePartitionFingerprint(self, partition):
517 try:
518 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
519 except ExternalError:
520 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
521 self.GetPartitionBuildProp("ro.product.brand", partition),
522 self.GetPartitionBuildProp("ro.product.name", partition),
523 self.GetPartitionBuildProp("ro.product.device", partition),
524 self.GetPartitionBuildProp("ro.build.version.release", partition),
525 self.GetPartitionBuildProp("ro.build.id", partition),
526 self.GetPartitionBuildProp("ro.build.version.incremental", partition),
527 self.GetPartitionBuildProp("ro.build.type", partition),
528 self.GetPartitionBuildProp("ro.build.tags", partition))
529
Tao Bao1c320f82019-10-04 23:25:12 -0700530 def CalculateFingerprint(self):
531 if self.oem_props is None:
532 try:
533 return self.GetBuildProp("ro.build.fingerprint")
534 except ExternalError:
535 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
536 self.GetBuildProp("ro.product.brand"),
537 self.GetBuildProp("ro.product.name"),
538 self.GetBuildProp("ro.product.device"),
539 self.GetBuildProp("ro.build.version.release"),
540 self.GetBuildProp("ro.build.id"),
541 self.GetBuildProp("ro.build.version.incremental"),
542 self.GetBuildProp("ro.build.type"),
543 self.GetBuildProp("ro.build.tags"))
544 return "%s/%s/%s:%s" % (
545 self.GetOemProperty("ro.product.brand"),
546 self.GetOemProperty("ro.product.name"),
547 self.GetOemProperty("ro.product.device"),
548 self.GetBuildProp("ro.build.thumbprint"))
549
550 def WriteMountOemScript(self, script):
551 assert self.oem_props is not None
552 recovery_mount_options = self.info_dict.get("recovery_mount_options")
553 script.Mount("/oem", recovery_mount_options)
554
555 def WriteDeviceAssertions(self, script, oem_no_mount):
556 # Read the property directly if not using OEM properties.
557 if not self.oem_props:
558 script.AssertDevice(self.device)
559 return
560
561 # Otherwise assert OEM properties.
562 if not self.oem_dicts:
563 raise ExternalError(
564 "No OEM file provided to answer expected assertions")
565
566 for prop in self.oem_props.split():
567 values = []
568 for oem_dict in self.oem_dicts:
569 if prop in oem_dict:
570 values.append(oem_dict[prop])
571 if not values:
572 raise ExternalError(
573 "The OEM file is missing the property %s" % (prop,))
574 script.AssertOemProperty(prop, values, oem_no_mount)
575
576
Tianjie Xu726d20a2020-05-09 05:24:18 +0000577def ReadFromInputFile(input_file, fn):
578 """Reads the contents of fn from input zipfile or directory."""
579 if isinstance(input_file, zipfile.ZipFile):
580 return input_file.read(fn).decode()
581 else:
582 path = os.path.join(input_file, *fn.split("/"))
583 try:
584 with open(path) as f:
585 return f.read()
586 except IOError as e:
587 if e.errno == errno.ENOENT:
588 raise KeyError(fn)
589
590
Tao Bao410ad8b2018-08-24 12:08:38 -0700591def LoadInfoDict(input_file, repacking=False):
592 """Loads the key/value pairs from the given input target_files.
593
594 It reads `META/misc_info.txt` file in the target_files input, does sanity
595 checks and returns the parsed key/value pairs for to the given build. It's
596 usually called early when working on input target_files files, e.g. when
597 generating OTAs, or signing builds. Note that the function may be called
598 against an old target_files file (i.e. from past dessert releases). So the
599 property parsing needs to be backward compatible.
600
601 In a `META/misc_info.txt`, a few properties are stored as links to the files
602 in the PRODUCT_OUT directory. It works fine with the build system. However,
603 they are no longer available when (re)generating images from target_files zip.
604 When `repacking` is True, redirect these properties to the actual files in the
605 unzipped directory.
606
607 Args:
608 input_file: The input target_files file, which could be an open
609 zipfile.ZipFile instance, or a str for the dir that contains the files
610 unzipped from a target_files file.
611 repacking: Whether it's trying repack an target_files file after loading the
612 info dict (default: False). If so, it will rewrite a few loaded
613 properties (e.g. selinux_fc, root_dir) to point to the actual files in
614 target_files file. When doing repacking, `input_file` must be a dir.
615
616 Returns:
617 A dict that contains the parsed key/value pairs.
618
619 Raises:
620 AssertionError: On invalid input arguments.
621 ValueError: On malformed input values.
622 """
623 if repacking:
624 assert isinstance(input_file, str), \
625 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700626
Doug Zongkerc9253822014-02-04 12:17:58 -0800627 def read_helper(fn):
Tianjie Xu726d20a2020-05-09 05:24:18 +0000628 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800629
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700630 try:
Michael Runge6e836112014-04-15 17:40:21 -0700631 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700632 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700633 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700634
Tao Bao410ad8b2018-08-24 12:08:38 -0700635 if "recovery_api_version" not in d:
636 raise ValueError("Failed to find 'recovery_api_version'")
637 if "fstab_version" not in d:
638 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800639
Tao Bao410ad8b2018-08-24 12:08:38 -0700640 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700641 # "selinux_fc" properties should point to the file_contexts files
642 # (file_contexts.bin) under META/.
643 for key in d:
644 if key.endswith("selinux_fc"):
645 fc_basename = os.path.basename(d[key])
646 fc_config = os.path.join(input_file, "META", fc_basename)
647 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700648
Daniel Norman72c626f2019-05-13 15:58:14 -0700649 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700650
Tom Cherryd14b8952018-08-09 14:26:00 -0700651 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700652 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700653 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700654 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700655
David Anderson0ec64ac2019-12-06 12:21:18 -0800656 # Redirect {partition}_base_fs_file for each of the named partitions.
657 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
658 key_name = part_name + "_base_fs_file"
659 if key_name not in d:
660 continue
661 basename = os.path.basename(d[key_name])
662 base_fs_file = os.path.join(input_file, "META", basename)
663 if os.path.exists(base_fs_file):
664 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700665 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700666 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800667 "Failed to find %s base fs file: %s", part_name, base_fs_file)
668 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700669
Doug Zongker37974732010-09-16 17:44:38 -0700670 def makeint(key):
671 if key in d:
672 d[key] = int(d[key], 0)
673
674 makeint("recovery_api_version")
675 makeint("blocksize")
676 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700677 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700678 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700679 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700680 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800681 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700682
Steve Muckle53226682020-05-07 17:32:10 -0700683 boot_images = "boot.img"
684 if "boot_images" in d:
685 boot_images = d["boot_images"]
686 for b in boot_images.split():
687 makeint(b.replace(".img","_size"))
688
Tao Bao765668f2019-10-04 22:03:00 -0700689 # Load recovery fstab if applicable.
690 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800691
Tianjie Xu861f4132018-09-12 11:49:33 -0700692 # Tries to load the build props for all partitions with care_map, including
693 # system and vendor.
694 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800695 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu726d20a2020-05-09 05:24:18 +0000696 d[partition_prop] = PartitionBuildProps.FromInputFile(
697 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700698 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800699
Tao Bao3ed35d32019-10-07 20:48:48 -0700700 # Set up the salt (based on fingerprint) that will be used when adding AVB
701 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800702 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700703 build_info = BuildInfo(d)
Daniel Normanab5acef2020-01-08 17:01:11 -0800704 for partition in PARTITIONS_WITH_CARE_MAP:
705 fingerprint = build_info.GetPartitionFingerprint(partition)
706 if fingerprint:
707 d["avb_{}_salt".format(partition)] = sha256(fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800708
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700709 return d
710
Tao Baod1de6f32017-03-01 16:38:48 -0800711
Daniel Norman4cc9df62019-07-18 10:11:07 -0700712def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900713 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700714 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900715
Daniel Norman4cc9df62019-07-18 10:11:07 -0700716
717def LoadDictionaryFromFile(file_path):
718 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900719 return LoadDictionaryFromLines(lines)
720
721
Michael Runge6e836112014-04-15 17:40:21 -0700722def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700723 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700724 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700725 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700726 if not line or line.startswith("#"):
727 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700728 if "=" in line:
729 name, value = line.split("=", 1)
730 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700731 return d
732
Tao Baod1de6f32017-03-01 16:38:48 -0800733
Tianjie Xu726d20a2020-05-09 05:24:18 +0000734class PartitionBuildProps(object):
735 """The class holds the build prop of a particular partition.
736
737 This class loads the build.prop and holds the build properties for a given
738 partition. It also partially recognizes the 'import' statement in the
739 build.prop; and calculates alternative values of some specific build
740 properties during runtime.
741
742 Attributes:
743 input_file: a zipped target-file or an unzipped target-file directory.
744 partition: name of the partition.
745 props_allow_override: a list of build properties to search for the
746 alternative values during runtime.
Tianjie Xu835aac52020-05-10 21:48:15 +0000747 build_props: a dict of build properties for the given partition.
748 prop_overrides: a set of props that are overridden by import.
749 placeholder_values: A dict of runtime variables' values to replace the
750 placeholders in the build.prop file. We expect exactly one value for
751 each of the variables.
Tianjie Xu726d20a2020-05-09 05:24:18 +0000752 """
Tianjie Xu835aac52020-05-10 21:48:15 +0000753 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu726d20a2020-05-09 05:24:18 +0000754 self.input_file = input_file
755 self.partition = name
756 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu835aac52020-05-10 21:48:15 +0000757 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu726d20a2020-05-09 05:24:18 +0000758 self.build_props = {}
Tianjie Xu835aac52020-05-10 21:48:15 +0000759 self.prop_overrides = set()
760 self.placeholder_values = {}
761 if placeholder_values:
762 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu726d20a2020-05-09 05:24:18 +0000763
764 @staticmethod
765 def FromDictionary(name, build_props):
766 """Constructs an instance from a build prop dictionary."""
767
768 props = PartitionBuildProps("unknown", name)
769 props.build_props = build_props.copy()
770 return props
771
772 @staticmethod
Tianjie Xu835aac52020-05-10 21:48:15 +0000773 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu726d20a2020-05-09 05:24:18 +0000774 """Loads the build.prop file and builds the attributes."""
Tianjie Xu726d20a2020-05-09 05:24:18 +0000775 data = ''
776 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
777 '{}/build.prop'.format(name.upper())]:
778 try:
779 data = ReadFromInputFile(input_file, prop_file)
780 break
781 except KeyError:
782 logger.warning('Failed to read %s', prop_file)
783
Tianjie Xu835aac52020-05-10 21:48:15 +0000784 props = PartitionBuildProps(input_file, name, placeholder_values)
785 props._LoadBuildProp(data)
Tianjie Xu726d20a2020-05-09 05:24:18 +0000786 return props
787
Tianjie Xu835aac52020-05-10 21:48:15 +0000788 def _LoadBuildProp(self, data):
789 for line in data.split('\n'):
790 line = line.strip()
791 if not line or line.startswith("#"):
792 continue
793 if line.startswith("import"):
794 overrides = self._ImportParser(line)
795 duplicates = self.prop_overrides.intersection(overrides.keys())
796 if duplicates:
797 raise ValueError('prop {} is overridden multiple times'.format(
798 ','.join(duplicates)))
799 self.prop_overrides = self.prop_overrides.union(overrides.keys())
800 self.build_props.update(overrides)
801 elif "=" in line:
802 name, value = line.split("=", 1)
803 if name in self.prop_overrides:
804 raise ValueError('prop {} is set again after overridden by import '
805 'statement'.format(name))
806 self.build_props[name] = value
807
808 def _ImportParser(self, line):
809 """Parses the build prop in a given import statement."""
810
811 tokens = line.split()
Hongguang Chen96c88282020-05-13 18:05:20 -0700812 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3) :
Tianjie Xu835aac52020-05-10 21:48:15 +0000813 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chen96c88282020-05-13 18:05:20 -0700814
815 if len(tokens) == 3:
816 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
817 return {}
818
Tianjie Xu835aac52020-05-10 21:48:15 +0000819 import_path = tokens[1]
820 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
821 raise ValueError('Unrecognized import path {}'.format(line))
822
823 # We only recognize a subset of import statement that the init process
824 # supports. And we can loose the restriction based on how the dynamic
825 # fingerprint is used in practice. The placeholder format should be
826 # ${placeholder}, and its value should be provided by the caller through
827 # the placeholder_values.
828 for prop, value in self.placeholder_values.items():
829 prop_place_holder = '${{{}}}'.format(prop)
830 if prop_place_holder in import_path:
831 import_path = import_path.replace(prop_place_holder, value)
832 if '$' in import_path:
833 logger.info('Unresolved place holder in import path %s', import_path)
834 return {}
835
836 import_path = import_path.replace('/{}'.format(self.partition),
837 self.partition.upper())
838 logger.info('Parsing build props override from %s', import_path)
839
840 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
841 d = LoadDictionaryFromLines(lines)
842 return {key: val for key, val in d.items()
843 if key in self.props_allow_override}
844
Tianjie Xu726d20a2020-05-09 05:24:18 +0000845 def GetProp(self, prop):
846 return self.build_props.get(prop)
847
848
Tianjie Xucfa86222016-03-07 16:31:19 -0800849def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
850 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700851 class Partition(object):
Yifan Hong7169f752020-04-17 10:08:10 -0700852 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700853 self.mount_point = mount_point
854 self.fs_type = fs_type
855 self.device = device
856 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700857 self.context = context
Yifan Hong7169f752020-04-17 10:08:10 -0700858 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700859
860 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800861 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700862 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700863 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700864 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700865
Tao Baod1de6f32017-03-01 16:38:48 -0800866 assert fstab_version == 2
867
868 d = {}
869 for line in data.split("\n"):
870 line = line.strip()
871 if not line or line.startswith("#"):
872 continue
873
874 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
875 pieces = line.split()
876 if len(pieces) != 5:
877 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
878
879 # Ignore entries that are managed by vold.
880 options = pieces[4]
881 if "voldmanaged=" in options:
882 continue
883
884 # It's a good line, parse it.
885 length = 0
Yifan Hong7169f752020-04-17 10:08:10 -0700886 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800887 options = options.split(",")
888 for i in options:
889 if i.startswith("length="):
890 length = int(i[7:])
Yifan Hong7169f752020-04-17 10:08:10 -0700891 elif i == "slotselect":
892 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800893 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800894 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700895 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800896
Tao Baod1de6f32017-03-01 16:38:48 -0800897 mount_flags = pieces[3]
898 # Honor the SELinux context if present.
899 context = None
900 for i in mount_flags.split(","):
901 if i.startswith("context="):
902 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800903
Tao Baod1de6f32017-03-01 16:38:48 -0800904 mount_point = pieces[1]
905 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong7169f752020-04-17 10:08:10 -0700906 device=pieces[0], length=length, context=context,
907 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800908
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700909 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700910 # system. Other areas assume system is always at "/system" so point /system
911 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700912 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800913 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700914 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700915 return d
916
917
Tao Bao765668f2019-10-04 22:03:00 -0700918def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
919 """Finds the path to recovery fstab and loads its contents."""
920 # recovery fstab is only meaningful when installing an update via recovery
921 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong7169f752020-04-17 10:08:10 -0700922 if info_dict.get('ab_update') == 'true' and \
923 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -0700924 return None
925
926 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
927 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
928 # cases, since it may load the info_dict from an old build (e.g. when
929 # generating incremental OTAs from that build).
930 system_root_image = info_dict.get('system_root_image') == 'true'
931 if info_dict.get('no_recovery') != 'true':
932 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
933 if isinstance(input_file, zipfile.ZipFile):
934 if recovery_fstab_path not in input_file.namelist():
935 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
936 else:
937 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
938 if not os.path.exists(path):
939 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
940 return LoadRecoveryFSTab(
941 read_helper, info_dict['fstab_version'], recovery_fstab_path,
942 system_root_image)
943
944 if info_dict.get('recovery_as_boot') == 'true':
945 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
946 if isinstance(input_file, zipfile.ZipFile):
947 if recovery_fstab_path not in input_file.namelist():
948 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
949 else:
950 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
951 if not os.path.exists(path):
952 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
953 return LoadRecoveryFSTab(
954 read_helper, info_dict['fstab_version'], recovery_fstab_path,
955 system_root_image)
956
957 return None
958
959
Doug Zongker37974732010-09-16 17:44:38 -0700960def DumpInfoDict(d):
961 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700962 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700963
Dan Albert8b72aef2015-03-23 19:13:21 -0700964
Daniel Norman55417142019-11-25 16:04:36 -0800965def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700966 """Merges dynamic partition info variables.
967
968 Args:
969 framework_dict: The dictionary of dynamic partition info variables from the
970 partial framework target files.
971 vendor_dict: The dictionary of dynamic partition info variables from the
972 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700973
974 Returns:
975 The merged dynamic partition info dictionary.
976 """
977 merged_dict = {}
978 # Partition groups and group sizes are defined by the vendor dict because
979 # these values may vary for each board that uses a shared system image.
980 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800981 framework_dynamic_partition_list = framework_dict.get(
982 "dynamic_partition_list", "")
983 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
984 merged_dict["dynamic_partition_list"] = ("%s %s" % (
985 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700986 for partition_group in merged_dict["super_partition_groups"].split(" "):
987 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800988 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700989 if key not in vendor_dict:
990 raise ValueError("Vendor dict does not contain required key %s." % key)
991 merged_dict[key] = vendor_dict[key]
992
993 # Set the partition group's partition list using a concatenation of the
994 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800995 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700996 merged_dict[key] = (
997 "%s %s" %
998 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530999
1000 # Pick virtual ab related flags from vendor dict, if defined.
1001 if "virtual_ab" in vendor_dict.keys():
1002 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
1003 if "virtual_ab_retrofit" in vendor_dict.keys():
1004 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001005 return merged_dict
1006
1007
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001008def AppendAVBSigningArgs(cmd, partition):
1009 """Append signing arguments for avbtool."""
1010 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1011 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001012 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1013 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1014 if os.path.exists(new_key_path):
1015 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001016 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1017 if key_path and algorithm:
1018 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001019 avb_salt = OPTIONS.info_dict.get("avb_salt")
1020 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001021 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001022 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001023
1024
Tao Bao765668f2019-10-04 22:03:00 -07001025def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001026 """Returns the VBMeta arguments for partition.
1027
1028 It sets up the VBMeta argument by including the partition descriptor from the
1029 given 'image', or by configuring the partition as a chained partition.
1030
1031 Args:
1032 partition: The name of the partition (e.g. "system").
1033 image: The path to the partition image.
1034 info_dict: A dict returned by common.LoadInfoDict(). Will use
1035 OPTIONS.info_dict if None has been given.
1036
1037 Returns:
1038 A list of VBMeta arguments.
1039 """
1040 if info_dict is None:
1041 info_dict = OPTIONS.info_dict
1042
1043 # Check if chain partition is used.
1044 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001045 if not key_path:
1046 return ["--include_descriptors_from_image", image]
1047
1048 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1049 # into vbmeta.img. The recovery image will be configured on an independent
1050 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1051 # See details at
1052 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001053 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001054 return []
1055
1056 # Otherwise chain the partition into vbmeta.
1057 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1058 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001059
1060
Tao Bao02a08592018-07-22 12:40:45 -07001061def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1062 """Constructs and returns the arg to build or verify a chained partition.
1063
1064 Args:
1065 partition: The partition name.
1066 info_dict: The info dict to look up the key info and rollback index
1067 location.
1068 key: The key to be used for building or verifying the partition. Defaults to
1069 the key listed in info_dict.
1070
1071 Returns:
1072 A string of form "partition:rollback_index_location:key" that can be used to
1073 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001074 """
1075 if key is None:
1076 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001077 if key and not os.path.exists(key) and OPTIONS.search_path:
1078 new_key_path = os.path.join(OPTIONS.search_path, key)
1079 if os.path.exists(new_key_path):
1080 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001081 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001082 rollback_index_location = info_dict[
1083 "avb_" + partition + "_rollback_index_location"]
1084 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1085
1086
Daniel Norman276f0622019-07-26 14:13:51 -07001087def BuildVBMeta(image_path, partitions, name, needed_partitions):
1088 """Creates a VBMeta image.
1089
1090 It generates the requested VBMeta image. The requested image could be for
1091 top-level or chained VBMeta image, which is determined based on the name.
1092
1093 Args:
1094 image_path: The output path for the new VBMeta image.
1095 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chend9edddd2020-04-27 18:36:36 -07001096 values. Only valid partition names are accepted, as partitions listed
1097 in common.AVB_PARTITIONS and custom partitions listed in
1098 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001099 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1100 needed_partitions: Partitions whose descriptors should be included into the
1101 generated VBMeta image.
1102
1103 Raises:
1104 AssertionError: On invalid input args.
1105 """
1106 avbtool = OPTIONS.info_dict["avb_avbtool"]
1107 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1108 AppendAVBSigningArgs(cmd, name)
1109
Hongguang Chend9edddd2020-04-27 18:36:36 -07001110 custom_partitions = OPTIONS.info_dict.get(
1111 "avb_custom_images_partition_list", "").strip().split()
1112
Daniel Norman276f0622019-07-26 14:13:51 -07001113 for partition, path in partitions.items():
1114 if partition not in needed_partitions:
1115 continue
1116 assert (partition in AVB_PARTITIONS or
Hongguang Chend9edddd2020-04-27 18:36:36 -07001117 partition in AVB_VBMETA_PARTITIONS or
1118 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001119 'Unknown partition: {}'.format(partition)
1120 assert os.path.exists(path), \
1121 'Failed to find {} for {}'.format(path, partition)
1122 cmd.extend(GetAvbPartitionArg(partition, path))
1123
1124 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1125 if args and args.strip():
1126 split_args = shlex.split(args)
1127 for index, arg in enumerate(split_args[:-1]):
1128 # Sanity check that the image file exists. Some images might be defined
1129 # as a path relative to source tree, which may not be available at the
1130 # same location when running this script (we have the input target_files
1131 # zip only). For such cases, we additionally scan other locations (e.g.
1132 # IMAGES/, RADIO/, etc) before bailing out.
1133 if arg == '--include_descriptors_from_image':
1134 image_path = split_args[index + 1]
1135 if os.path.exists(image_path):
1136 continue
1137 found = False
1138 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1139 alt_path = os.path.join(
1140 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
1141 if os.path.exists(alt_path):
1142 split_args[index + 1] = alt_path
1143 found = True
1144 break
1145 assert found, 'Failed to find {}'.format(image_path)
1146 cmd.extend(split_args)
1147
1148 RunAndCheckOutput(cmd)
1149
Dan Austin52903642019-12-12 15:44:00 -08001150 if OPTIONS.aftl_server is not None:
1151 # Ensure the other AFTL parameters are set as well.
1152 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1153 assert OPTIONS.aftl_manufacturer_key_path is not None, 'No AFTL manufacturer key provided.'
1154 assert OPTIONS.aftl_signer_helper is not None, 'No AFTL signer helper provided.'
1155 # AFTL inclusion proof generation code will go here.
Daniel Norman276f0622019-07-26 14:13:51 -07001156
Luca Stefani412fef62020-06-11 13:03:18 +02001157def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False, xz_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001158 ramdisk_img = tempfile.NamedTemporaryFile()
1159
1160 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1161 cmd = ["mkbootfs", "-f", fs_config_file,
1162 os.path.join(sourcedir, "RAMDISK")]
1163 else:
1164 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1165 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avilad30b75182020-06-10 20:09:10 +00001166 if lz4_ramdisks:
1167 p2 = Run(["lz4", "-l", "-12" , "--favor-decSpeed"], stdin=p1.stdout,
1168 stdout=ramdisk_img.file.fileno())
Luca Stefani412fef62020-06-11 13:03:18 +02001169 elif xz_ramdisks:
1170 p2 = Run(["xz", "-f", "-c", "--check=crc32", "--lzma2=dict=32MiB"], stdin=p1.stdout,
1171 stdout=ramdisk_img.file.fileno())
J. Avilad30b75182020-06-10 20:09:10 +00001172 else:
1173 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001174
1175 p2.wait()
1176 p1.wait()
1177 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avilad30b75182020-06-10 20:09:10 +00001178 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001179
1180 return ramdisk_img
1181
1182
Steve Mucklef83e3c32020-04-08 18:27:00 -07001183def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001184 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001185 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001186
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001187 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001188 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1189 we are building a two-step special image (i.e. building a recovery image to
1190 be loaded into /boot in two-step OTAs).
1191
1192 Return the image data, or None if sourcedir does not appear to contains files
1193 for building the requested image.
1194 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001195
Steve Mucklef83e3c32020-04-08 18:27:00 -07001196 # "boot" or "recovery", without extension.
1197 partition_name = os.path.basename(sourcedir).lower()
1198
1199 if partition_name == "recovery":
1200 kernel = "kernel"
1201 else:
1202 kernel = image_name.replace("boot", "kernel")
1203 kernel = kernel.replace(".img","")
1204 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001205 return None
1206
1207 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001208 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001209
Doug Zongkerd5131602012-08-02 14:46:42 -07001210 if info_dict is None:
1211 info_dict = OPTIONS.info_dict
1212
Doug Zongkereef39442009-04-02 12:14:19 -07001213 img = tempfile.NamedTemporaryFile()
1214
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001215 if has_ramdisk:
J. Avilad30b75182020-06-10 20:09:10 +00001216 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
Luca Stefani412fef62020-06-11 13:03:18 +02001217 use_xz = info_dict.get("xz_ramdisks") == 'true'
1218 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4, xz_ramdisks=use_xz)
Doug Zongkereef39442009-04-02 12:14:19 -07001219
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001220 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1221 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1222
Steve Mucklef83e3c32020-04-08 18:27:00 -07001223 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001224
Benoit Fradina45a8682014-07-14 21:00:43 +02001225 fn = os.path.join(sourcedir, "second")
1226 if os.access(fn, os.F_OK):
1227 cmd.append("--second")
1228 cmd.append(fn)
1229
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001230 fn = os.path.join(sourcedir, "dtb")
1231 if os.access(fn, os.F_OK):
1232 cmd.append("--dtb")
1233 cmd.append(fn)
1234
Doug Zongker171f1cd2009-06-15 22:36:37 -07001235 fn = os.path.join(sourcedir, "cmdline")
1236 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001237 cmd.append("--cmdline")
1238 cmd.append(open(fn).read().rstrip("\n"))
1239
1240 fn = os.path.join(sourcedir, "base")
1241 if os.access(fn, os.F_OK):
1242 cmd.append("--base")
1243 cmd.append(open(fn).read().rstrip("\n"))
1244
Ying Wang4de6b5b2010-08-25 14:29:34 -07001245 fn = os.path.join(sourcedir, "pagesize")
1246 if os.access(fn, os.F_OK):
1247 cmd.append("--pagesize")
1248 cmd.append(open(fn).read().rstrip("\n"))
1249
David Ngdd5ed212012-07-27 18:39:48 -07001250 fn = os.path.join(sourcedir, "dt")
1251 if os.access(fn, os.F_OK):
1252 cmd.append("--dt")
1253 cmd.append(fn)
1254
Steve Muckle759d0c82020-03-16 19:13:46 -07001255 if partition_name == "recovery":
1256 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddye4d5d562020-05-04 19:40:16 +05301257 if not args:
1258 # Fall back to "mkbootimg_args" for recovery image
1259 # in case "recovery_mkbootimg_args" is not set.
1260 args = info_dict.get("mkbootimg_args")
Steve Muckle759d0c82020-03-16 19:13:46 -07001261 else:
1262 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001263 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001264 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001265
Tao Bao76def242017-11-21 09:25:31 -08001266 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001267 if args and args.strip():
1268 cmd.extend(shlex.split(args))
1269
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001270 if has_ramdisk:
1271 cmd.extend(["--ramdisk", ramdisk_img.name])
1272
Tao Baod95e9fd2015-03-29 23:07:41 -07001273 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001274 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001275 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001276 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001277 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001278 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001279
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001280 if partition_name == "recovery":
1281 if info_dict.get("include_recovery_dtbo") == "true":
1282 fn = os.path.join(sourcedir, "recovery_dtbo")
1283 cmd.extend(["--recovery_dtbo", fn])
1284 if info_dict.get("include_recovery_acpio") == "true":
1285 fn = os.path.join(sourcedir, "recovery_acpio")
1286 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001287
Tao Bao986ee862018-10-04 15:46:16 -07001288 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001289
Tao Bao76def242017-11-21 09:25:31 -08001290 if (info_dict.get("boot_signer") == "true" and
1291 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001292 # Hard-code the path as "/boot" for two-step special recovery image (which
1293 # will be loaded into /boot during the two-step OTA).
1294 if two_step_image:
1295 path = "/boot"
1296 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001297 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001298 cmd = [OPTIONS.boot_signer_path]
1299 cmd.extend(OPTIONS.boot_signer_args)
1300 cmd.extend([path, img.name,
1301 info_dict["verity_key"] + ".pk8",
1302 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001303 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001304
Tao Baod95e9fd2015-03-29 23:07:41 -07001305 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001306 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001307 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001308 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001309 # We have switched from the prebuilt futility binary to using the tool
1310 # (futility-host) built from the source. Override the setting in the old
1311 # TF.zip.
1312 futility = info_dict["futility"]
1313 if futility.startswith("prebuilts/"):
1314 futility = "futility-host"
1315 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001316 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001317 info_dict["vboot_key"] + ".vbprivk",
1318 info_dict["vboot_subkey"] + ".vbprivk",
1319 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001320 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001321 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001322
Tao Baof3282b42015-04-01 11:21:55 -07001323 # Clean up the temp files.
1324 img_unsigned.close()
1325 img_keyblock.close()
1326
David Zeuthen8fecb282017-12-01 16:24:01 -05001327 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001328 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001329 avbtool = info_dict["avb_avbtool"]
Steve Muckle53226682020-05-07 17:32:10 -07001330 if partition_name == "recovery":
1331 part_size = info_dict["recovery_size"]
1332 else:
1333 part_size = info_dict[image_name.replace(".img","_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001334 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001335 "--partition_size", str(part_size), "--partition_name",
1336 partition_name]
1337 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001338 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001339 if args and args.strip():
1340 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001341 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001342
1343 img.seek(os.SEEK_SET, 0)
1344 data = img.read()
1345
1346 if has_ramdisk:
1347 ramdisk_img.close()
1348 img.close()
1349
1350 return data
1351
1352
Doug Zongkerd5131602012-08-02 14:46:42 -07001353def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001354 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001355 """Return a File object with the desired bootable image.
1356
1357 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1358 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1359 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001360
Doug Zongker55d93282011-01-25 17:03:34 -08001361 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1362 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001363 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001364 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001365
1366 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1367 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001368 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001369 return File.FromLocalFile(name, prebuilt_path)
1370
Tao Bao32fcdab2018-10-12 10:30:39 -07001371 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001372
1373 if info_dict is None:
1374 info_dict = OPTIONS.info_dict
1375
1376 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001377 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1378 # for recovery.
1379 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1380 prebuilt_name != "boot.img" or
1381 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001382
Doug Zongker6f1d0312014-08-22 08:07:12 -07001383 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Mucklef83e3c32020-04-08 18:27:00 -07001384 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001385 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001386 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001387 if data:
1388 return File(name, data)
1389 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001390
Doug Zongkereef39442009-04-02 12:14:19 -07001391
Steve Mucklee1b10862019-07-10 10:49:37 -07001392def _BuildVendorBootImage(sourcedir, info_dict=None):
1393 """Build a vendor boot image from the specified sourcedir.
1394
1395 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1396 turn them into a vendor boot image.
1397
1398 Return the image data, or None if sourcedir does not appear to contains files
1399 for building the requested image.
1400 """
1401
1402 if info_dict is None:
1403 info_dict = OPTIONS.info_dict
1404
1405 img = tempfile.NamedTemporaryFile()
1406
J. Avilad30b75182020-06-10 20:09:10 +00001407 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
Luca Stefani412fef62020-06-11 13:03:18 +02001408 use_xz = info_dict.get("xz_ramdisks") == 'true'
1409 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4, xz_ramdisks=use_xz)
Steve Mucklee1b10862019-07-10 10:49:37 -07001410
1411 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1412 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1413
1414 cmd = [mkbootimg]
1415
1416 fn = os.path.join(sourcedir, "dtb")
1417 if os.access(fn, os.F_OK):
1418 cmd.append("--dtb")
1419 cmd.append(fn)
1420
1421 fn = os.path.join(sourcedir, "vendor_cmdline")
1422 if os.access(fn, os.F_OK):
1423 cmd.append("--vendor_cmdline")
1424 cmd.append(open(fn).read().rstrip("\n"))
1425
1426 fn = os.path.join(sourcedir, "base")
1427 if os.access(fn, os.F_OK):
1428 cmd.append("--base")
1429 cmd.append(open(fn).read().rstrip("\n"))
1430
1431 fn = os.path.join(sourcedir, "pagesize")
1432 if os.access(fn, os.F_OK):
1433 cmd.append("--pagesize")
1434 cmd.append(open(fn).read().rstrip("\n"))
1435
1436 args = info_dict.get("mkbootimg_args")
1437 if args and args.strip():
1438 cmd.extend(shlex.split(args))
1439
1440 args = info_dict.get("mkbootimg_version_args")
1441 if args and args.strip():
1442 cmd.extend(shlex.split(args))
1443
1444 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1445 cmd.extend(["--vendor_boot", img.name])
1446
1447 RunAndCheckOutput(cmd)
1448
1449 # AVB: if enabled, calculate and add hash.
1450 if info_dict.get("avb_enable") == "true":
1451 avbtool = info_dict["avb_avbtool"]
1452 part_size = info_dict["vendor_boot_size"]
1453 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001454 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001455 AppendAVBSigningArgs(cmd, "vendor_boot")
1456 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1457 if args and args.strip():
1458 cmd.extend(shlex.split(args))
1459 RunAndCheckOutput(cmd)
1460
1461 img.seek(os.SEEK_SET, 0)
1462 data = img.read()
1463
1464 ramdisk_img.close()
1465 img.close()
1466
1467 return data
1468
1469
1470def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1471 info_dict=None):
1472 """Return a File object with the desired vendor boot image.
1473
1474 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1475 the source files in 'unpack_dir'/'tree_subdir'."""
1476
1477 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1478 if os.path.exists(prebuilt_path):
1479 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1480 return File.FromLocalFile(name, prebuilt_path)
1481
1482 logger.info("building image from target_files %s...", tree_subdir)
1483
1484 if info_dict is None:
1485 info_dict = OPTIONS.info_dict
1486
1487 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1488 if data:
1489 return File(name, data)
1490 return None
1491
1492
Narayan Kamatha07bf042017-08-14 14:49:21 +01001493def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001494 """Gunzips the given gzip compressed file to a given output file."""
1495 with gzip.open(in_filename, "rb") as in_file, \
1496 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001497 shutil.copyfileobj(in_file, out_file)
1498
1499
Tao Bao0ff15de2019-03-20 11:26:06 -07001500def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001501 """Unzips the archive to the given directory.
1502
1503 Args:
1504 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001505 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001506 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1507 archvie. Non-matching patterns will be filtered out. If there's no match
1508 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001509 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001510 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001511 if patterns is not None:
1512 # Filter out non-matching patterns. unzip will complain otherwise.
1513 with zipfile.ZipFile(filename) as input_zip:
1514 names = input_zip.namelist()
1515 filtered = [
1516 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1517
1518 # There isn't any matching files. Don't unzip anything.
1519 if not filtered:
1520 return
1521 cmd.extend(filtered)
1522
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001523 RunAndCheckOutput(cmd)
1524
1525
Doug Zongker75f17362009-12-08 13:46:44 -08001526def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001527 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001528
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001529 Args:
1530 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1531 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1532
1533 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1534 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001535
Tao Bao1c830bf2017-12-25 10:43:47 -08001536 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001537 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001538 """
Doug Zongkereef39442009-04-02 12:14:19 -07001539
Tao Bao1c830bf2017-12-25 10:43:47 -08001540 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001541 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1542 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001543 UnzipToDir(m.group(1), tmp, pattern)
1544 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001545 filename = m.group(1)
1546 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001547 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001548
Tao Baodba59ee2018-01-09 13:21:02 -08001549 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001550
1551
Yifan Hong8a66a712019-04-04 15:37:57 -07001552def GetUserImage(which, tmpdir, input_zip,
1553 info_dict=None,
1554 allow_shared_blocks=None,
1555 hashtree_info_generator=None,
1556 reset_file_map=False):
1557 """Returns an Image object suitable for passing to BlockImageDiff.
1558
1559 This function loads the specified image from the given path. If the specified
1560 image is sparse, it also performs additional processing for OTA purpose. For
1561 example, it always adds block 0 to clobbered blocks list. It also detects
1562 files that cannot be reconstructed from the block list, for whom we should
1563 avoid applying imgdiff.
1564
1565 Args:
1566 which: The partition name.
1567 tmpdir: The directory that contains the prebuilt image and block map file.
1568 input_zip: The target-files ZIP archive.
1569 info_dict: The dict to be looked up for relevant info.
1570 allow_shared_blocks: If image is sparse, whether having shared blocks is
1571 allowed. If none, it is looked up from info_dict.
1572 hashtree_info_generator: If present and image is sparse, generates the
1573 hashtree_info for this sparse image.
1574 reset_file_map: If true and image is sparse, reset file map before returning
1575 the image.
1576 Returns:
1577 A Image object. If it is a sparse image and reset_file_map is False, the
1578 image will have file_map info loaded.
1579 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001580 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001581 info_dict = LoadInfoDict(input_zip)
1582
1583 is_sparse = info_dict.get("extfs_sparse_flag")
1584
1585 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1586 # shared blocks (i.e. some blocks will show up in multiple files' block
1587 # list). We can only allocate such shared blocks to the first "owner", and
1588 # disable imgdiff for all later occurrences.
1589 if allow_shared_blocks is None:
1590 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1591
1592 if is_sparse:
1593 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1594 hashtree_info_generator)
1595 if reset_file_map:
1596 img.ResetFileMap()
1597 return img
1598 else:
1599 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1600
1601
1602def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1603 """Returns a Image object suitable for passing to BlockImageDiff.
1604
1605 This function loads the specified non-sparse image from the given path.
1606
1607 Args:
1608 which: The partition name.
1609 tmpdir: The directory that contains the prebuilt image and block map file.
1610 Returns:
1611 A Image object.
1612 """
1613 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1614 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1615
1616 # The image and map files must have been created prior to calling
1617 # ota_from_target_files.py (since LMP).
1618 assert os.path.exists(path) and os.path.exists(mappath)
1619
Tianjie Xu41976c72019-07-03 13:57:01 -07001620 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1621
Yifan Hong8a66a712019-04-04 15:37:57 -07001622
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001623def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1624 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001625 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1626
1627 This function loads the specified sparse image from the given path, and
1628 performs additional processing for OTA purpose. For example, it always adds
1629 block 0 to clobbered blocks list. It also detects files that cannot be
1630 reconstructed from the block list, for whom we should avoid applying imgdiff.
1631
1632 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001633 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001634 tmpdir: The directory that contains the prebuilt image and block map file.
1635 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001636 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001637 hashtree_info_generator: If present, generates the hashtree_info for this
1638 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001639 Returns:
1640 A SparseImage object, with file_map info loaded.
1641 """
Tao Baoc765cca2018-01-31 17:32:40 -08001642 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1643 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1644
1645 # The image and map files must have been created prior to calling
1646 # ota_from_target_files.py (since LMP).
1647 assert os.path.exists(path) and os.path.exists(mappath)
1648
1649 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1650 # it to clobbered_blocks so that it will be written to the target
1651 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1652 clobbered_blocks = "0"
1653
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001654 image = sparse_img.SparseImage(
1655 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1656 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001657
1658 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1659 # if they contain all zeros. We can't reconstruct such a file from its block
1660 # list. Tag such entries accordingly. (Bug: 65213616)
1661 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001662 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001663 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001664 continue
1665
Tom Cherryd14b8952018-08-09 14:26:00 -07001666 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1667 # filename listed in system.map may contain an additional leading slash
1668 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1669 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001670 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001671
Tom Cherryd14b8952018-08-09 14:26:00 -07001672 # Special handling another case, where files not under /system
1673 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001674 if which == 'system' and not arcname.startswith('SYSTEM'):
1675 arcname = 'ROOT/' + arcname
1676
1677 assert arcname in input_zip.namelist(), \
1678 "Failed to find the ZIP entry for {}".format(entry)
1679
Tao Baoc765cca2018-01-31 17:32:40 -08001680 info = input_zip.getinfo(arcname)
1681 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001682
1683 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001684 # image, check the original block list to determine its completeness. Note
1685 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001686 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001687 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001688
Tao Baoc765cca2018-01-31 17:32:40 -08001689 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1690 ranges.extra['incomplete'] = True
1691
1692 return image
1693
1694
Doug Zongkereef39442009-04-02 12:14:19 -07001695def GetKeyPasswords(keylist):
1696 """Given a list of keys, prompt the user to enter passwords for
1697 those which require them. Return a {key: password} dict. password
1698 will be None if the key has no password."""
1699
Doug Zongker8ce7c252009-05-22 13:34:54 -07001700 no_passwords = []
1701 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001702 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001703 devnull = open("/dev/null", "w+b")
1704 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001705 # We don't need a password for things that aren't really keys.
1706 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001707 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001708 continue
1709
T.R. Fullhart37e10522013-03-18 10:31:26 -07001710 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001711 "-inform", "DER", "-nocrypt"],
1712 stdin=devnull.fileno(),
1713 stdout=devnull.fileno(),
1714 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001715 p.communicate()
1716 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001717 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001718 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001719 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001720 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1721 "-inform", "DER", "-passin", "pass:"],
1722 stdin=devnull.fileno(),
1723 stdout=devnull.fileno(),
1724 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001725 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001726 if p.returncode == 0:
1727 # Encrypted key with empty string as password.
1728 key_passwords[k] = ''
1729 elif stderr.startswith('Error decrypting key'):
1730 # Definitely encrypted key.
1731 # It would have said "Error reading key" if it didn't parse correctly.
1732 need_passwords.append(k)
1733 else:
1734 # Potentially, a type of key that openssl doesn't understand.
1735 # We'll let the routines in signapk.jar handle it.
1736 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001737 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001738
T.R. Fullhart37e10522013-03-18 10:31:26 -07001739 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001740 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001741 return key_passwords
1742
1743
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001744def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001745 """Gets the minSdkVersion declared in the APK.
1746
changho.shin0f125362019-07-08 10:59:00 +09001747 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001748 This can be both a decimal number (API Level) or a codename.
1749
1750 Args:
1751 apk_name: The APK filename.
1752
1753 Returns:
1754 The parsed SDK version string.
1755
1756 Raises:
1757 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001758 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001759 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001760 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001761 stderr=subprocess.PIPE)
1762 stdoutdata, stderrdata = proc.communicate()
1763 if proc.returncode != 0:
1764 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001765 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001766 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001767
Tao Baof47bf0f2018-03-21 23:28:51 -07001768 for line in stdoutdata.split("\n"):
1769 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001770 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1771 if m:
1772 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001773 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001774
1775
1776def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001777 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001778
Tao Baof47bf0f2018-03-21 23:28:51 -07001779 If minSdkVersion is set to a codename, it is translated to a number using the
1780 provided map.
1781
1782 Args:
1783 apk_name: The APK filename.
1784
1785 Returns:
1786 The parsed SDK version number.
1787
1788 Raises:
1789 ExternalError: On failing to get the min SDK version number.
1790 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001791 version = GetMinSdkVersion(apk_name)
1792 try:
1793 return int(version)
1794 except ValueError:
1795 # Not a decimal number. Codename?
1796 if version in codename_to_api_level_map:
1797 return codename_to_api_level_map[version]
1798 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001799 raise ExternalError(
1800 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1801 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001802
1803
1804def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001805 codename_to_api_level_map=None, whole_file=False,
1806 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001807 """Sign the input_name zip/jar/apk, producing output_name. Use the
1808 given key and password (the latter may be None if the key does not
1809 have a password.
1810
Doug Zongker951495f2009-08-14 12:44:19 -07001811 If whole_file is true, use the "-w" option to SignApk to embed a
1812 signature that covers the whole file in the archive comment of the
1813 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001814
1815 min_api_level is the API Level (int) of the oldest platform this file may end
1816 up on. If not specified for an APK, the API Level is obtained by interpreting
1817 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1818
1819 codename_to_api_level_map is needed to translate the codename which may be
1820 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001821
1822 Caller may optionally specify extra args to be passed to SignApk, which
1823 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001824 """
Tao Bao76def242017-11-21 09:25:31 -08001825 if codename_to_api_level_map is None:
1826 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001827 if extra_signapk_args is None:
1828 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001829
Alex Klyubin9667b182015-12-10 13:38:50 -08001830 java_library_path = os.path.join(
1831 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1832
Tao Baoe95540e2016-11-08 12:08:53 -08001833 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1834 ["-Djava.library.path=" + java_library_path,
1835 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001836 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001837 if whole_file:
1838 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001839
1840 min_sdk_version = min_api_level
1841 if min_sdk_version is None:
1842 if not whole_file:
1843 min_sdk_version = GetMinSdkVersionInt(
1844 input_name, codename_to_api_level_map)
1845 if min_sdk_version is not None:
1846 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1847
T.R. Fullhart37e10522013-03-18 10:31:26 -07001848 cmd.extend([key + OPTIONS.public_key_suffix,
1849 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001850 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001851
Tao Bao73dd4f42018-10-04 16:25:33 -07001852 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001853 if password is not None:
1854 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001855 stdoutdata, _ = proc.communicate(password)
1856 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001857 raise ExternalError(
1858 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001859 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001860
Doug Zongkereef39442009-04-02 12:14:19 -07001861
Doug Zongker37974732010-09-16 17:44:38 -07001862def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001863 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001864
Tao Bao9dd909e2017-11-14 11:27:32 -08001865 For non-AVB images, raise exception if the data is too big. Print a warning
1866 if the data is nearing the maximum size.
1867
1868 For AVB images, the actual image size should be identical to the limit.
1869
1870 Args:
1871 data: A string that contains all the data for the partition.
1872 target: The partition name. The ".img" suffix is optional.
1873 info_dict: The dict to be looked up for relevant info.
1874 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001875 if target.endswith(".img"):
1876 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001877 mount_point = "/" + target
1878
Ying Wangf8824af2014-06-03 14:07:27 -07001879 fs_type = None
1880 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001881 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001882 if mount_point == "/userdata":
1883 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001884 p = info_dict["fstab"][mount_point]
1885 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001886 device = p.device
1887 if "/" in device:
1888 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001889 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001890 if not fs_type or not limit:
1891 return
Doug Zongkereef39442009-04-02 12:14:19 -07001892
Andrew Boie0f9aec82012-02-14 09:32:52 -08001893 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001894 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1895 # path.
1896 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1897 if size != limit:
1898 raise ExternalError(
1899 "Mismatching image size for %s: expected %d actual %d" % (
1900 target, limit, size))
1901 else:
1902 pct = float(size) * 100.0 / limit
1903 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1904 if pct >= 99.0:
1905 raise ExternalError(msg)
1906 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001907 logger.warning("\n WARNING: %s\n", msg)
1908 else:
1909 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001910
1911
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001912def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001913 """Parses the APK certs info from a given target-files zip.
1914
1915 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1916 tuple with the following elements: (1) a dictionary that maps packages to
1917 certs (based on the "certificate" and "private_key" attributes in the file;
1918 (2) a string representing the extension of compressed APKs in the target files
1919 (e.g ".gz", ".bro").
1920
1921 Args:
1922 tf_zip: The input target_files ZipFile (already open).
1923
1924 Returns:
1925 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1926 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1927 no compressed APKs.
1928 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001929 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001930 compressed_extension = None
1931
Tao Bao0f990332017-09-08 19:02:54 -07001932 # META/apkcerts.txt contains the info for _all_ the packages known at build
1933 # time. Filter out the ones that are not installed.
1934 installed_files = set()
1935 for name in tf_zip.namelist():
1936 basename = os.path.basename(name)
1937 if basename:
1938 installed_files.add(basename)
1939
Tao Baoda30cfa2017-12-01 16:19:46 -08001940 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001941 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001942 if not line:
1943 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001944 m = re.match(
1945 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham96c9e6e2020-04-03 15:36:23 -07001946 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1947 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001948 line)
1949 if not m:
1950 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001951
Tao Bao818ddf52018-01-05 11:17:34 -08001952 matches = m.groupdict()
1953 cert = matches["CERT"]
1954 privkey = matches["PRIVKEY"]
1955 name = matches["NAME"]
1956 this_compressed_extension = matches["COMPRESSED"]
1957
1958 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1959 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1960 if cert in SPECIAL_CERT_STRINGS and not privkey:
1961 certmap[name] = cert
1962 elif (cert.endswith(OPTIONS.public_key_suffix) and
1963 privkey.endswith(OPTIONS.private_key_suffix) and
1964 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1965 certmap[name] = cert[:-public_key_suffix_len]
1966 else:
1967 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1968
1969 if not this_compressed_extension:
1970 continue
1971
1972 # Only count the installed files.
1973 filename = name + '.' + this_compressed_extension
1974 if filename not in installed_files:
1975 continue
1976
1977 # Make sure that all the values in the compression map have the same
1978 # extension. We don't support multiple compression methods in the same
1979 # system image.
1980 if compressed_extension:
1981 if this_compressed_extension != compressed_extension:
1982 raise ValueError(
1983 "Multiple compressed extensions: {} vs {}".format(
1984 compressed_extension, this_compressed_extension))
1985 else:
1986 compressed_extension = this_compressed_extension
1987
1988 return (certmap,
1989 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001990
1991
Doug Zongkereef39442009-04-02 12:14:19 -07001992COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001993Global options
1994
1995 -p (--path) <dir>
1996 Prepend <dir>/bin to the list of places to search for binaries run by this
1997 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001998
Doug Zongker05d3dea2009-06-22 11:32:31 -07001999 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002000 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002001
Tao Bao30df8b42018-04-23 15:32:53 -07002002 -x (--extra) <key=value>
2003 Add a key/value pair to the 'extras' dict, which device-specific extension
2004 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002005
Doug Zongkereef39442009-04-02 12:14:19 -07002006 -v (--verbose)
2007 Show command lines being executed.
2008
2009 -h (--help)
2010 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002011
2012 --logfile <file>
2013 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002014"""
2015
2016def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002017 print(docstring.rstrip("\n"))
2018 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002019
2020
2021def ParseOptions(argv,
2022 docstring,
2023 extra_opts="", extra_long_opts=(),
2024 extra_option_handler=None):
2025 """Parse the options in argv and return any arguments that aren't
2026 flags. docstring is the calling module's docstring, to be displayed
2027 for errors and -h. extra_opts and extra_long_opts are for flags
2028 defined by the caller, which are processed by passing them to
2029 extra_option_handler."""
2030
2031 try:
2032 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002033 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002034 ["help", "verbose", "path=", "signapk_path=",
2035 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002036 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002037 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2038 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Dan Austin52903642019-12-12 15:44:00 -08002039 "extra=", "logfile=", "aftl_server=", "aftl_key_path=",
2040 "aftl_manufacturer_key_path=", "aftl_signer_helper="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07002041 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002042 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002043 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002044 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002045 sys.exit(2)
2046
Doug Zongkereef39442009-04-02 12:14:19 -07002047 for o, a in opts:
2048 if o in ("-h", "--help"):
2049 Usage(docstring)
2050 sys.exit()
2051 elif o in ("-v", "--verbose"):
2052 OPTIONS.verbose = True
2053 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002054 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002055 elif o in ("--signapk_path",):
2056 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002057 elif o in ("--signapk_shared_library_path",):
2058 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002059 elif o in ("--extra_signapk_args",):
2060 OPTIONS.extra_signapk_args = shlex.split(a)
2061 elif o in ("--java_path",):
2062 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002063 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002064 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002065 elif o in ("--android_jar_path",):
2066 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002067 elif o in ("--public_key_suffix",):
2068 OPTIONS.public_key_suffix = a
2069 elif o in ("--private_key_suffix",):
2070 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002071 elif o in ("--boot_signer_path",):
2072 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002073 elif o in ("--boot_signer_args",):
2074 OPTIONS.boot_signer_args = shlex.split(a)
2075 elif o in ("--verity_signer_path",):
2076 OPTIONS.verity_signer_path = a
2077 elif o in ("--verity_signer_args",):
2078 OPTIONS.verity_signer_args = shlex.split(a)
Dan Austin52903642019-12-12 15:44:00 -08002079 elif o in ("--aftl_server",):
2080 OPTIONS.aftl_server = a
2081 elif o in ("--aftl_key_path",):
2082 OPTIONS.aftl_key_path = a
2083 elif o in ("--aftl_manufacturer_key_path",):
2084 OPTIONS.aftl_manufacturer_key_path = a
2085 elif o in ("--aftl_signer_helper",):
2086 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002087 elif o in ("-s", "--device_specific"):
2088 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002089 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002090 key, value = a.split("=", 1)
2091 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002092 elif o in ("--logfile",):
2093 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002094 else:
2095 if extra_option_handler is None or not extra_option_handler(o, a):
2096 assert False, "unknown option \"%s\"" % (o,)
2097
Doug Zongker85448772014-09-09 14:59:20 -07002098 if OPTIONS.search_path:
2099 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2100 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002101
2102 return args
2103
2104
Tao Bao4c851b12016-09-19 13:54:38 -07002105def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002106 """Make a temp file and add it to the list of things to be deleted
2107 when Cleanup() is called. Return the filename."""
2108 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2109 os.close(fd)
2110 OPTIONS.tempfiles.append(fn)
2111 return fn
2112
2113
Tao Bao1c830bf2017-12-25 10:43:47 -08002114def MakeTempDir(prefix='tmp', suffix=''):
2115 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2116
2117 Returns:
2118 The absolute pathname of the new directory.
2119 """
2120 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2121 OPTIONS.tempfiles.append(dir_name)
2122 return dir_name
2123
2124
Doug Zongkereef39442009-04-02 12:14:19 -07002125def Cleanup():
2126 for i in OPTIONS.tempfiles:
2127 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002128 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002129 else:
2130 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002131 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002132
2133
2134class PasswordManager(object):
2135 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002136 self.editor = os.getenv("EDITOR")
2137 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002138
2139 def GetPasswords(self, items):
2140 """Get passwords corresponding to each string in 'items',
2141 returning a dict. (The dict may have keys in addition to the
2142 values in 'items'.)
2143
2144 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2145 user edit that file to add more needed passwords. If no editor is
2146 available, or $ANDROID_PW_FILE isn't define, prompts the user
2147 interactively in the ordinary way.
2148 """
2149
2150 current = self.ReadFile()
2151
2152 first = True
2153 while True:
2154 missing = []
2155 for i in items:
2156 if i not in current or not current[i]:
2157 missing.append(i)
2158 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002159 if not missing:
2160 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002161
2162 for i in missing:
2163 current[i] = ""
2164
2165 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002166 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002167 if sys.version_info[0] >= 3:
2168 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002169 answer = raw_input("try to edit again? [y]> ").strip()
2170 if answer and answer[0] not in 'yY':
2171 raise RuntimeError("key passwords unavailable")
2172 first = False
2173
2174 current = self.UpdateAndReadFile(current)
2175
Dan Albert8b72aef2015-03-23 19:13:21 -07002176 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002177 """Prompt the user to enter a value (password) for each key in
2178 'current' whose value is fales. Returns a new dict with all the
2179 values.
2180 """
2181 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002182 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002183 if v:
2184 result[k] = v
2185 else:
2186 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002187 result[k] = getpass.getpass(
2188 "Enter password for %s key> " % k).strip()
2189 if result[k]:
2190 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002191 return result
2192
2193 def UpdateAndReadFile(self, current):
2194 if not self.editor or not self.pwfile:
2195 return self.PromptResult(current)
2196
2197 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002198 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002199 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2200 f.write("# (Additional spaces are harmless.)\n\n")
2201
2202 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002203 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002204 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002205 f.write("[[[ %s ]]] %s\n" % (v, k))
2206 if not v and first_line is None:
2207 # position cursor on first line with no password.
2208 first_line = i + 4
2209 f.close()
2210
Tao Bao986ee862018-10-04 15:46:16 -07002211 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002212
2213 return self.ReadFile()
2214
2215 def ReadFile(self):
2216 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002217 if self.pwfile is None:
2218 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002219 try:
2220 f = open(self.pwfile, "r")
2221 for line in f:
2222 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002223 if not line or line[0] == '#':
2224 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002225 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2226 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002227 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002228 else:
2229 result[m.group(2)] = m.group(1)
2230 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002231 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002232 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002233 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002234 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002235
2236
Dan Albert8e0178d2015-01-27 15:53:15 -08002237def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2238 compress_type=None):
2239 import datetime
2240
2241 # http://b/18015246
2242 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2243 # for files larger than 2GiB. We can work around this by adjusting their
2244 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2245 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2246 # it isn't clear to me exactly what circumstances cause this).
2247 # `zipfile.write()` must be used directly to work around this.
2248 #
2249 # This mess can be avoided if we port to python3.
2250 saved_zip64_limit = zipfile.ZIP64_LIMIT
2251 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2252
2253 if compress_type is None:
2254 compress_type = zip_file.compression
2255 if arcname is None:
2256 arcname = filename
2257
2258 saved_stat = os.stat(filename)
2259
2260 try:
2261 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2262 # file to be zipped and reset it when we're done.
2263 os.chmod(filename, perms)
2264
2265 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002266 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2267 # intentional. zip stores datetimes in local time without a time zone
2268 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2269 # in the zip archive.
2270 local_epoch = datetime.datetime.fromtimestamp(0)
2271 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002272 os.utime(filename, (timestamp, timestamp))
2273
2274 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2275 finally:
2276 os.chmod(filename, saved_stat.st_mode)
2277 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2278 zipfile.ZIP64_LIMIT = saved_zip64_limit
2279
2280
Tao Bao58c1b962015-05-20 09:32:18 -07002281def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002282 compress_type=None):
2283 """Wrap zipfile.writestr() function to work around the zip64 limit.
2284
2285 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2286 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2287 when calling crc32(bytes).
2288
2289 But it still works fine to write a shorter string into a large zip file.
2290 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2291 when we know the string won't be too long.
2292 """
2293
2294 saved_zip64_limit = zipfile.ZIP64_LIMIT
2295 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2296
2297 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2298 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002299 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002300 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002301 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002302 else:
Tao Baof3282b42015-04-01 11:21:55 -07002303 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002304 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2305 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2306 # such a case (since
2307 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2308 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2309 # permission bits. We follow the logic in Python 3 to get consistent
2310 # behavior between using the two versions.
2311 if not zinfo.external_attr:
2312 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002313
2314 # If compress_type is given, it overrides the value in zinfo.
2315 if compress_type is not None:
2316 zinfo.compress_type = compress_type
2317
Tao Bao58c1b962015-05-20 09:32:18 -07002318 # If perms is given, it has a priority.
2319 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002320 # If perms doesn't set the file type, mark it as a regular file.
2321 if perms & 0o770000 == 0:
2322 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002323 zinfo.external_attr = perms << 16
2324
Tao Baof3282b42015-04-01 11:21:55 -07002325 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002326 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2327
Dan Albert8b72aef2015-03-23 19:13:21 -07002328 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002329 zipfile.ZIP64_LIMIT = saved_zip64_limit
2330
2331
Tao Bao89d7ab22017-12-14 17:05:33 -08002332def ZipDelete(zip_filename, entries):
2333 """Deletes entries from a ZIP file.
2334
2335 Since deleting entries from a ZIP file is not supported, it shells out to
2336 'zip -d'.
2337
2338 Args:
2339 zip_filename: The name of the ZIP file.
2340 entries: The name of the entry, or the list of names to be deleted.
2341
2342 Raises:
2343 AssertionError: In case of non-zero return from 'zip'.
2344 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002345 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002346 entries = [entries]
2347 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002348 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002349
2350
Tao Baof3282b42015-04-01 11:21:55 -07002351def ZipClose(zip_file):
2352 # http://b/18015246
2353 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2354 # central directory.
2355 saved_zip64_limit = zipfile.ZIP64_LIMIT
2356 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2357
2358 zip_file.close()
2359
2360 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002361
2362
2363class DeviceSpecificParams(object):
2364 module = None
2365 def __init__(self, **kwargs):
2366 """Keyword arguments to the constructor become attributes of this
2367 object, which is passed to all functions in the device-specific
2368 module."""
Tao Bao38884282019-07-10 22:20:56 -07002369 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002370 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002371 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002372
2373 if self.module is None:
2374 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002375 if not path:
2376 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002377 try:
2378 if os.path.isdir(path):
2379 info = imp.find_module("releasetools", [path])
2380 else:
2381 d, f = os.path.split(path)
2382 b, x = os.path.splitext(f)
2383 if x == ".py":
2384 f = b
2385 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002386 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002387 self.module = imp.load_module("device_specific", *info)
2388 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002389 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002390
2391 def _DoCall(self, function_name, *args, **kwargs):
2392 """Call the named function in the device-specific module, passing
2393 the given args and kwargs. The first argument to the call will be
2394 the DeviceSpecific object itself. If there is no module, or the
2395 module does not define the function, return the value of the
2396 'default' kwarg (which itself defaults to None)."""
2397 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002398 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002399 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2400
2401 def FullOTA_Assertions(self):
2402 """Called after emitting the block of assertions at the top of a
2403 full OTA package. Implementations can add whatever additional
2404 assertions they like."""
2405 return self._DoCall("FullOTA_Assertions")
2406
Doug Zongkere5ff5902012-01-17 10:55:37 -08002407 def FullOTA_InstallBegin(self):
2408 """Called at the start of full OTA installation."""
2409 return self._DoCall("FullOTA_InstallBegin")
2410
Yifan Hong10c530d2018-12-27 17:34:18 -08002411 def FullOTA_GetBlockDifferences(self):
2412 """Called during full OTA installation and verification.
2413 Implementation should return a list of BlockDifference objects describing
2414 the update on each additional partitions.
2415 """
2416 return self._DoCall("FullOTA_GetBlockDifferences")
2417
Doug Zongker05d3dea2009-06-22 11:32:31 -07002418 def FullOTA_InstallEnd(self):
2419 """Called at the end of full OTA installation; typically this is
2420 used to install the image for the device's baseband processor."""
2421 return self._DoCall("FullOTA_InstallEnd")
2422
M1chaa73c8ce2014-11-25 15:30:48 +01002423 def FullOTA_PostValidate(self):
2424 """Called after installing and validating /system; typically this is
2425 used to resize the system partition after a block based installation."""
2426 return self._DoCall("FullOTA_PostValidate")
2427
Doug Zongker05d3dea2009-06-22 11:32:31 -07002428 def IncrementalOTA_Assertions(self):
2429 """Called after emitting the block of assertions at the top of an
2430 incremental OTA package. Implementations can add whatever
2431 additional assertions they like."""
2432 return self._DoCall("IncrementalOTA_Assertions")
2433
Doug Zongkere5ff5902012-01-17 10:55:37 -08002434 def IncrementalOTA_VerifyBegin(self):
2435 """Called at the start of the verification phase of incremental
2436 OTA installation; additional checks can be placed here to abort
2437 the script before any changes are made."""
2438 return self._DoCall("IncrementalOTA_VerifyBegin")
2439
Doug Zongker05d3dea2009-06-22 11:32:31 -07002440 def IncrementalOTA_VerifyEnd(self):
2441 """Called at the end of the verification phase of incremental OTA
2442 installation; additional checks can be placed here to abort the
2443 script before any changes are made."""
2444 return self._DoCall("IncrementalOTA_VerifyEnd")
2445
Doug Zongkere5ff5902012-01-17 10:55:37 -08002446 def IncrementalOTA_InstallBegin(self):
2447 """Called at the start of incremental OTA installation (after
2448 verification is complete)."""
2449 return self._DoCall("IncrementalOTA_InstallBegin")
2450
Yifan Hong10c530d2018-12-27 17:34:18 -08002451 def IncrementalOTA_GetBlockDifferences(self):
2452 """Called during incremental OTA installation and verification.
2453 Implementation should return a list of BlockDifference objects describing
2454 the update on each additional partitions.
2455 """
2456 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2457
Doug Zongker05d3dea2009-06-22 11:32:31 -07002458 def IncrementalOTA_InstallEnd(self):
2459 """Called at the end of incremental OTA installation; typically
2460 this is used to install the image for the device's baseband
2461 processor."""
2462 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002463
Tao Bao9bc6bb22015-11-09 16:58:28 -08002464 def VerifyOTA_Assertions(self):
2465 return self._DoCall("VerifyOTA_Assertions")
2466
Tao Bao76def242017-11-21 09:25:31 -08002467
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002468class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002469 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002470 self.name = name
2471 self.data = data
2472 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002473 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002474 self.sha1 = sha1(data).hexdigest()
2475
2476 @classmethod
2477 def FromLocalFile(cls, name, diskname):
2478 f = open(diskname, "rb")
2479 data = f.read()
2480 f.close()
2481 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002482
2483 def WriteToTemp(self):
2484 t = tempfile.NamedTemporaryFile()
2485 t.write(self.data)
2486 t.flush()
2487 return t
2488
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002489 def WriteToDir(self, d):
2490 with open(os.path.join(d, self.name), "wb") as fp:
2491 fp.write(self.data)
2492
Geremy Condra36bd3652014-02-06 19:45:10 -08002493 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002494 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002495
Tao Bao76def242017-11-21 09:25:31 -08002496
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002497DIFF_PROGRAM_BY_EXT = {
2498 ".gz" : "imgdiff",
2499 ".zip" : ["imgdiff", "-z"],
2500 ".jar" : ["imgdiff", "-z"],
2501 ".apk" : ["imgdiff", "-z"],
2502 ".img" : "imgdiff",
2503 }
2504
Tao Bao76def242017-11-21 09:25:31 -08002505
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002506class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002507 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002508 self.tf = tf
2509 self.sf = sf
2510 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002511 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002512
2513 def ComputePatch(self):
2514 """Compute the patch (as a string of data) needed to turn sf into
2515 tf. Returns the same tuple as GetPatch()."""
2516
2517 tf = self.tf
2518 sf = self.sf
2519
Doug Zongker24cd2802012-08-14 16:36:15 -07002520 if self.diff_program:
2521 diff_program = self.diff_program
2522 else:
2523 ext = os.path.splitext(tf.name)[1]
2524 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002525
2526 ttemp = tf.WriteToTemp()
2527 stemp = sf.WriteToTemp()
2528
2529 ext = os.path.splitext(tf.name)[1]
2530
2531 try:
2532 ptemp = tempfile.NamedTemporaryFile()
2533 if isinstance(diff_program, list):
2534 cmd = copy.copy(diff_program)
2535 else:
2536 cmd = [diff_program]
2537 cmd.append(stemp.name)
2538 cmd.append(ttemp.name)
2539 cmd.append(ptemp.name)
2540 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002541 err = []
2542 def run():
2543 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002544 if e:
2545 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002546 th = threading.Thread(target=run)
2547 th.start()
2548 th.join(timeout=300) # 5 mins
2549 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002550 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002551 p.terminate()
2552 th.join(5)
2553 if th.is_alive():
2554 p.kill()
2555 th.join()
2556
Tianjie Xua2a9f992018-01-05 15:15:54 -08002557 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002558 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002559 self.patch = None
2560 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002561 diff = ptemp.read()
2562 finally:
2563 ptemp.close()
2564 stemp.close()
2565 ttemp.close()
2566
2567 self.patch = diff
2568 return self.tf, self.sf, self.patch
2569
2570
2571 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002572 """Returns a tuple of (target_file, source_file, patch_data).
2573
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002574 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002575 computing the patch failed.
2576 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002577 return self.tf, self.sf, self.patch
2578
2579
2580def ComputeDifferences(diffs):
2581 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002582 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002583
2584 # Do the largest files first, to try and reduce the long-pole effect.
2585 by_size = [(i.tf.size, i) for i in diffs]
2586 by_size.sort(reverse=True)
2587 by_size = [i[1] for i in by_size]
2588
2589 lock = threading.Lock()
2590 diff_iter = iter(by_size) # accessed under lock
2591
2592 def worker():
2593 try:
2594 lock.acquire()
2595 for d in diff_iter:
2596 lock.release()
2597 start = time.time()
2598 d.ComputePatch()
2599 dur = time.time() - start
2600 lock.acquire()
2601
2602 tf, sf, patch = d.GetPatch()
2603 if sf.name == tf.name:
2604 name = tf.name
2605 else:
2606 name = "%s (%s)" % (tf.name, sf.name)
2607 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002608 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002609 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002610 logger.info(
2611 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2612 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002613 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002614 except Exception:
2615 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002616 raise
2617
2618 # start worker threads; wait for them all to finish.
2619 threads = [threading.Thread(target=worker)
2620 for i in range(OPTIONS.worker_threads)]
2621 for th in threads:
2622 th.start()
2623 while threads:
2624 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002625
2626
Dan Albert8b72aef2015-03-23 19:13:21 -07002627class BlockDifference(object):
2628 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002629 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002630 self.tgt = tgt
2631 self.src = src
2632 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002633 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002634 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002635
Tao Baodd2a5892015-03-12 12:32:37 -07002636 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002637 version = max(
2638 int(i) for i in
2639 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002640 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002641 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002642
Tianjie Xu41976c72019-07-03 13:57:01 -07002643 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2644 version=self.version,
2645 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002646 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002647 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002648 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002649 self.touched_src_ranges = b.touched_src_ranges
2650 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002651
Yifan Hong10c530d2018-12-27 17:34:18 -08002652 # On devices with dynamic partitions, for new partitions,
2653 # src is None but OPTIONS.source_info_dict is not.
2654 if OPTIONS.source_info_dict is None:
2655 is_dynamic_build = OPTIONS.info_dict.get(
2656 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002657 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002658 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002659 is_dynamic_build = OPTIONS.source_info_dict.get(
2660 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002661 is_dynamic_source = partition in shlex.split(
2662 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002663
Yifan Hongbb2658d2019-01-25 12:30:58 -08002664 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002665 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2666
Yifan Hongbb2658d2019-01-25 12:30:58 -08002667 # For dynamic partitions builds, check partition list in both source
2668 # and target build because new partitions may be added, and existing
2669 # partitions may be removed.
2670 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2671
Yifan Hong10c530d2018-12-27 17:34:18 -08002672 if is_dynamic:
2673 self.device = 'map_partition("%s")' % partition
2674 else:
2675 if OPTIONS.source_info_dict is None:
Yifan Hongae6e0d52020-05-07 12:38:53 -07002676 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2677 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002678 else:
Yifan Hongae6e0d52020-05-07 12:38:53 -07002679 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2680 OPTIONS.source_info_dict)
2681 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002682
Tao Baod8d14be2016-02-04 14:26:02 -08002683 @property
2684 def required_cache(self):
2685 return self._required_cache
2686
Tao Bao76def242017-11-21 09:25:31 -08002687 def WriteScript(self, script, output_zip, progress=None,
2688 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002689 if not self.src:
2690 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002691 script.Print("Patching %s image unconditionally..." % (self.partition,))
2692 else:
2693 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002694
Dan Albert8b72aef2015-03-23 19:13:21 -07002695 if progress:
2696 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002697 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002698
2699 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002700 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002701
Tao Bao9bc6bb22015-11-09 16:58:28 -08002702 def WriteStrictVerifyScript(self, script):
2703 """Verify all the blocks in the care_map, including clobbered blocks.
2704
2705 This differs from the WriteVerifyScript() function: a) it prints different
2706 error messages; b) it doesn't allow half-way updated images to pass the
2707 verification."""
2708
2709 partition = self.partition
2710 script.Print("Verifying %s..." % (partition,))
2711 ranges = self.tgt.care_map
2712 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002713 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002714 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2715 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002716 self.device, ranges_str,
2717 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002718 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002719 script.AppendExtra("")
2720
Tao Baod522bdc2016-04-12 15:53:16 -07002721 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002722 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002723
2724 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002725 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002726 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002727
2728 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002729 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002730 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002731 ranges = self.touched_src_ranges
2732 expected_sha1 = self.touched_src_sha1
2733 else:
2734 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2735 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002736
2737 # No blocks to be checked, skipping.
2738 if not ranges:
2739 return
2740
Tao Bao5ece99d2015-05-12 11:42:31 -07002741 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002742 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002743 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002744 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2745 '"%s.patch.dat")) then' % (
2746 self.device, ranges_str, expected_sha1,
2747 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002748 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002749 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002750
Tianjie Xufc3422a2015-12-15 11:53:59 -08002751 if self.version >= 4:
2752
2753 # Bug: 21124327
2754 # When generating incrementals for the system and vendor partitions in
2755 # version 4 or newer, explicitly check the first block (which contains
2756 # the superblock) of the partition to see if it's what we expect. If
2757 # this check fails, give an explicit log message about the partition
2758 # having been remounted R/W (the most likely explanation).
2759 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002760 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002761
2762 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002763 if partition == "system":
2764 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2765 else:
2766 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002767 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002768 'ifelse (block_image_recover({device}, "{ranges}") && '
2769 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002770 'package_extract_file("{partition}.transfer.list"), '
2771 '"{partition}.new.dat", "{partition}.patch.dat"), '
2772 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002773 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002774 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002775 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002776
Tao Baodd2a5892015-03-12 12:32:37 -07002777 # Abort the OTA update. Note that the incremental OTA cannot be applied
2778 # even if it may match the checksum of the target partition.
2779 # a) If version < 3, operations like move and erase will make changes
2780 # unconditionally and damage the partition.
2781 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002782 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002783 if partition == "system":
2784 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2785 else:
2786 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2787 script.AppendExtra((
2788 'abort("E%d: %s partition has unexpected contents");\n'
2789 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002790
Yifan Hong10c530d2018-12-27 17:34:18 -08002791 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002792 partition = self.partition
2793 script.Print('Verifying the updated %s image...' % (partition,))
2794 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2795 ranges = self.tgt.care_map
2796 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002797 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002798 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002799 self.device, ranges_str,
2800 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002801
2802 # Bug: 20881595
2803 # Verify that extended blocks are really zeroed out.
2804 if self.tgt.extended:
2805 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002806 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002807 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002808 self.device, ranges_str,
2809 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002810 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002811 if partition == "system":
2812 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2813 else:
2814 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002815 script.AppendExtra(
2816 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002817 ' abort("E%d: %s partition has unexpected non-zero contents after '
2818 'OTA update");\n'
2819 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002820 else:
2821 script.Print('Verified the updated %s image.' % (partition,))
2822
Tianjie Xu209db462016-05-24 17:34:52 -07002823 if partition == "system":
2824 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2825 else:
2826 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2827
Tao Bao5fcaaef2015-06-01 13:40:49 -07002828 script.AppendExtra(
2829 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002830 ' abort("E%d: %s partition has unexpected contents after OTA '
2831 'update");\n'
2832 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002833
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002834 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002835 ZipWrite(output_zip,
2836 '{}.transfer.list'.format(self.path),
2837 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002838
Tao Bao76def242017-11-21 09:25:31 -08002839 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2840 # its size. Quailty 9 almost triples the compression time but doesn't
2841 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002842 # zip | brotli(quality 6) | brotli(quality 9)
2843 # compressed_size: 942M | 869M (~8% reduced) | 854M
2844 # compression_time: 75s | 265s | 719s
2845 # decompression_time: 15s | 25s | 25s
2846
2847 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002848 brotli_cmd = ['brotli', '--quality=6',
2849 '--output={}.new.dat.br'.format(self.path),
2850 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002851 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002852 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002853
2854 new_data_name = '{}.new.dat.br'.format(self.partition)
2855 ZipWrite(output_zip,
2856 '{}.new.dat.br'.format(self.path),
2857 new_data_name,
2858 compress_type=zipfile.ZIP_STORED)
2859 else:
2860 new_data_name = '{}.new.dat'.format(self.partition)
2861 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2862
Dan Albert8e0178d2015-01-27 15:53:15 -08002863 ZipWrite(output_zip,
2864 '{}.patch.dat'.format(self.path),
2865 '{}.patch.dat'.format(self.partition),
2866 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002867
Tianjie Xu209db462016-05-24 17:34:52 -07002868 if self.partition == "system":
2869 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2870 else:
2871 code = ErrorCode.VENDOR_UPDATE_FAILURE
2872
Yifan Hong10c530d2018-12-27 17:34:18 -08002873 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002874 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002875 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002876 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002877 device=self.device, partition=self.partition,
2878 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002879 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002880
Ido Ben-Hura2545e22019-11-29 22:15:37 +02002881 call = ('delete_recursive("/data/system/package_cache");')
2882 script.AppendExtra(script.WordWrap(call))
2883
2884
Dan Albert8b72aef2015-03-23 19:13:21 -07002885 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002886 data = source.ReadRangeSet(ranges)
2887 ctx = sha1()
2888
2889 for p in data:
2890 ctx.update(p)
2891
2892 return ctx.hexdigest()
2893
Tao Baoe9b61912015-07-09 17:37:49 -07002894 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2895 """Return the hash value for all zero blocks."""
2896 zero_block = '\x00' * 4096
2897 ctx = sha1()
2898 for _ in range(num_blocks):
2899 ctx.update(zero_block)
2900
2901 return ctx.hexdigest()
2902
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002903
Tianjie Xu41976c72019-07-03 13:57:01 -07002904# Expose these two classes to support vendor-specific scripts
2905DataImage = images.DataImage
2906EmptyImage = images.EmptyImage
2907
Tao Bao76def242017-11-21 09:25:31 -08002908
Doug Zongker96a57e72010-09-26 14:57:41 -07002909# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002910PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002911 "ext4": "EMMC",
2912 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002913 "f2fs": "EMMC",
2914 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002915}
Doug Zongker96a57e72010-09-26 14:57:41 -07002916
Yifan Hongae6e0d52020-05-07 12:38:53 -07002917def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2918 """
2919 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2920 backwards compatibility. It aborts if the fstab entry has slotselect option
2921 (unless check_no_slot is explicitly set to False).
2922 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002923 fstab = info["fstab"]
2924 if fstab:
Yifan Hongae6e0d52020-05-07 12:38:53 -07002925 if check_no_slot:
2926 assert not fstab[mount_point].slotselect, \
2927 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002928 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2929 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002930 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002931 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002932
2933
Yifan Hongae6e0d52020-05-07 12:38:53 -07002934def GetTypeAndDeviceExpr(mount_point, info):
2935 """
2936 Return the filesystem of the partition, and an edify expression that evaluates
2937 to the device at runtime.
2938 """
2939 fstab = info["fstab"]
2940 if fstab:
2941 p = fstab[mount_point]
2942 device_expr = '"%s"' % fstab[mount_point].device
2943 if p.slotselect:
2944 device_expr = 'add_slot_suffix(%s)' % device_expr
2945 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
2946 else:
2947 raise KeyError
2948
2949
2950def GetEntryForDevice(fstab, device):
2951 """
2952 Returns:
2953 The first entry in fstab whose device is the given value.
2954 """
2955 if not fstab:
2956 return None
2957 for mount_point in fstab:
2958 if fstab[mount_point].device == device:
2959 return fstab[mount_point]
2960 return None
2961
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002962def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002963 """Parses and converts a PEM-encoded certificate into DER-encoded.
2964
2965 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2966
2967 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002968 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002969 """
2970 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002971 save = False
2972 for line in data.split("\n"):
2973 if "--END CERTIFICATE--" in line:
2974 break
2975 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002976 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002977 if "--BEGIN CERTIFICATE--" in line:
2978 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002979 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002980 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002981
Tao Bao04e1f012018-02-04 12:13:35 -08002982
2983def ExtractPublicKey(cert):
2984 """Extracts the public key (PEM-encoded) from the given certificate file.
2985
2986 Args:
2987 cert: The certificate filename.
2988
2989 Returns:
2990 The public key string.
2991
2992 Raises:
2993 AssertionError: On non-zero return from 'openssl'.
2994 """
2995 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2996 # While openssl 1.1 writes the key into the given filename followed by '-out',
2997 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2998 # stdout instead.
2999 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3000 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3001 pubkey, stderrdata = proc.communicate()
3002 assert proc.returncode == 0, \
3003 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3004 return pubkey
3005
3006
Tao Bao1ac886e2019-06-26 11:58:22 -07003007def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003008 """Extracts the AVB public key from the given public or private key.
3009
3010 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003011 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003012 key: The input key file, which should be PEM-encoded public or private key.
3013
3014 Returns:
3015 The path to the extracted AVB public key file.
3016 """
3017 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3018 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003019 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003020 return output
3021
3022
Doug Zongker412c02f2014-02-13 10:58:24 -08003023def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3024 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003025 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003026
Tao Bao6d5d6232018-03-09 17:04:42 -08003027 Most of the space in the boot and recovery images is just the kernel, which is
3028 identical for the two, so the resulting patch should be efficient. Add it to
3029 the output zip, along with a shell script that is run from init.rc on first
3030 boot to actually do the patching and install the new recovery image.
3031
3032 Args:
3033 input_dir: The top-level input directory of the target-files.zip.
3034 output_sink: The callback function that writes the result.
3035 recovery_img: File object for the recovery image.
3036 boot_img: File objects for the boot image.
3037 info_dict: A dict returned by common.LoadInfoDict() on the input
3038 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003039 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003040 if info_dict is None:
3041 info_dict = OPTIONS.info_dict
3042
Tao Bao6d5d6232018-03-09 17:04:42 -08003043 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003044 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3045
3046 if board_uses_vendorimage:
3047 # In this case, the output sink is rooted at VENDOR
3048 recovery_img_path = "etc/recovery.img"
3049 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
Bill Peckhame868aec2019-09-17 17:06:47 -07003050 else:
3051 # In this case the output sink is rooted at SYSTEM
3052 recovery_img_path = "vendor/etc/recovery.img"
3053 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
Doug Zongkerc9253822014-02-04 12:17:58 -08003054
Tao Baof2cffbd2015-07-22 12:33:18 -07003055 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003056 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003057
3058 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003059 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003060 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003061 # With system-root-image, boot and recovery images will have mismatching
3062 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3063 # to handle such a case.
3064 if system_root_image:
3065 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003066 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003067 assert not os.path.exists(path)
3068 else:
3069 diff_program = ["imgdiff"]
3070 if os.path.exists(path):
3071 diff_program.append("-b")
3072 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003073 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003074 else:
3075 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003076
3077 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3078 _, _, patch = d.ComputePatch()
3079 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003080
Dan Albertebb19aa2015-03-27 19:11:53 -07003081 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003082 # The following GetTypeAndDevice()s need to use the path in the target
3083 # info_dict instead of source_info_dict.
Yifan Hongae6e0d52020-05-07 12:38:53 -07003084 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3085 check_no_slot=False)
3086 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3087 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003088 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003089 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003090
Tao Baof2cffbd2015-07-22 12:33:18 -07003091 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003092
3093 # Note that we use /vendor to refer to the recovery resources. This will
3094 # work for a separate vendor partition mounted at /vendor or a
3095 # /system/vendor subdirectory on the system partition, for which init will
3096 # create a symlink from /vendor to /system/vendor.
3097
3098 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003099if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3100 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003101 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003102 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3103 log -t recovery "Installing new recovery image: succeeded" || \\
3104 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003105else
3106 log -t recovery "Recovery image already installed"
3107fi
3108""" % {'type': recovery_type,
3109 'device': recovery_device,
3110 'sha1': recovery_img.sha1,
3111 'size': recovery_img.size}
3112 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003113 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003114if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3115 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003116 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003117 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3118 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3119 log -t recovery "Installing new recovery image: succeeded" || \\
3120 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003121else
3122 log -t recovery "Recovery image already installed"
3123fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003124""" % {'boot_size': boot_img.size,
3125 'boot_sha1': boot_img.sha1,
3126 'recovery_size': recovery_img.size,
3127 'recovery_sha1': recovery_img.sha1,
3128 'boot_type': boot_type,
Yifan Hongae6e0d52020-05-07 12:38:53 -07003129 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee55f62c2020-05-19 13:44:26 -07003130 'recovery_type': recovery_type,
3131 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003132 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003133
Bill Peckhame868aec2019-09-17 17:06:47 -07003134 # The install script location moved from /system/etc to /system/bin in the L
3135 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
dianlujitaoaea9af62020-09-12 13:48:26 +08003136 output_sink("bin/install-recovery.sh", sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003137
3138
3139class DynamicPartitionUpdate(object):
3140 def __init__(self, src_group=None, tgt_group=None, progress=None,
3141 block_difference=None):
3142 self.src_group = src_group
3143 self.tgt_group = tgt_group
3144 self.progress = progress
3145 self.block_difference = block_difference
3146
3147 @property
3148 def src_size(self):
3149 if not self.block_difference:
3150 return 0
3151 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3152
3153 @property
3154 def tgt_size(self):
3155 if not self.block_difference:
3156 return 0
3157 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3158
3159 @staticmethod
3160 def _GetSparseImageSize(img):
3161 if not img:
3162 return 0
3163 return img.blocksize * img.total_blocks
3164
3165
3166class DynamicGroupUpdate(object):
3167 def __init__(self, src_size=None, tgt_size=None):
3168 # None: group does not exist. 0: no size limits.
3169 self.src_size = src_size
3170 self.tgt_size = tgt_size
3171
3172
3173class DynamicPartitionsDifference(object):
3174 def __init__(self, info_dict, block_diffs, progress_dict=None,
Peter Cai9f6c7d82020-03-01 14:43:57 +08003175 source_info_dict=None, build_without_vendor=False):
Yifan Hong10c530d2018-12-27 17:34:18 -08003176 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003177 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003178
Peter Cai9f6c7d82020-03-01 14:43:57 +08003179 self._build_without_vendor = build_without_vendor
Yifan Hong10c530d2018-12-27 17:34:18 -08003180 self._remove_all_before_apply = False
3181 if source_info_dict is None:
3182 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003183 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003184
Tao Baof1113e92019-06-18 12:10:14 -07003185 block_diff_dict = collections.OrderedDict(
3186 [(e.partition, e) for e in block_diffs])
3187
Yifan Hong10c530d2018-12-27 17:34:18 -08003188 assert len(block_diff_dict) == len(block_diffs), \
3189 "Duplicated BlockDifference object for {}".format(
3190 [partition for partition, count in
3191 collections.Counter(e.partition for e in block_diffs).items()
3192 if count > 1])
3193
Yifan Hong79997e52019-01-23 16:56:19 -08003194 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003195
3196 for p, block_diff in block_diff_dict.items():
3197 self._partition_updates[p] = DynamicPartitionUpdate()
3198 self._partition_updates[p].block_difference = block_diff
3199
3200 for p, progress in progress_dict.items():
3201 if p in self._partition_updates:
3202 self._partition_updates[p].progress = progress
3203
3204 tgt_groups = shlex.split(info_dict.get(
3205 "super_partition_groups", "").strip())
3206 src_groups = shlex.split(source_info_dict.get(
3207 "super_partition_groups", "").strip())
3208
3209 for g in tgt_groups:
3210 for p in shlex.split(info_dict.get(
3211 "super_%s_partition_list" % g, "").strip()):
3212 assert p in self._partition_updates, \
3213 "{} is in target super_{}_partition_list but no BlockDifference " \
3214 "object is provided.".format(p, g)
3215 self._partition_updates[p].tgt_group = g
3216
3217 for g in src_groups:
3218 for p in shlex.split(source_info_dict.get(
3219 "super_%s_partition_list" % g, "").strip()):
3220 assert p in self._partition_updates, \
3221 "{} is in source super_{}_partition_list but no BlockDifference " \
3222 "object is provided.".format(p, g)
3223 self._partition_updates[p].src_group = g
3224
Yifan Hong45433e42019-01-18 13:55:25 -08003225 target_dynamic_partitions = set(shlex.split(info_dict.get(
3226 "dynamic_partition_list", "").strip()))
3227 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3228 if u.tgt_size)
3229 assert block_diffs_with_target == target_dynamic_partitions, \
3230 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3231 list(target_dynamic_partitions), list(block_diffs_with_target))
3232
3233 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3234 "dynamic_partition_list", "").strip()))
3235 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3236 if u.src_size)
3237 assert block_diffs_with_source == source_dynamic_partitions, \
3238 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3239 list(source_dynamic_partitions), list(block_diffs_with_source))
3240
Yifan Hong10c530d2018-12-27 17:34:18 -08003241 if self._partition_updates:
3242 logger.info("Updating dynamic partitions %s",
3243 self._partition_updates.keys())
3244
Yifan Hong79997e52019-01-23 16:56:19 -08003245 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003246
3247 for g in tgt_groups:
3248 self._group_updates[g] = DynamicGroupUpdate()
3249 self._group_updates[g].tgt_size = int(info_dict.get(
3250 "super_%s_group_size" % g, "0").strip())
3251
3252 for g in src_groups:
3253 if g not in self._group_updates:
3254 self._group_updates[g] = DynamicGroupUpdate()
3255 self._group_updates[g].src_size = int(source_info_dict.get(
3256 "super_%s_group_size" % g, "0").strip())
3257
3258 self._Compute()
3259
3260 def WriteScript(self, script, output_zip, write_verify_script=False):
3261 script.Comment('--- Start patching dynamic partitions ---')
3262 for p, u in self._partition_updates.items():
3263 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3264 script.Comment('Patch partition %s' % p)
3265 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3266 write_verify_script=False)
3267
3268 op_list_path = MakeTempFile()
3269 with open(op_list_path, 'w') as f:
3270 for line in self._op_list:
3271 f.write('{}\n'.format(line))
3272
3273 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3274
3275 script.Comment('Update dynamic partition metadata')
3276 script.AppendExtra('assert(update_dynamic_partitions('
3277 'package_extract_file("dynamic_partitions_op_list")));')
3278
3279 if write_verify_script:
3280 for p, u in self._partition_updates.items():
3281 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3282 u.block_difference.WritePostInstallVerifyScript(script)
3283 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3284
3285 for p, u in self._partition_updates.items():
3286 if u.tgt_size and u.src_size <= u.tgt_size:
3287 script.Comment('Patch partition %s' % p)
3288 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3289 write_verify_script=write_verify_script)
3290 if write_verify_script:
3291 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3292
3293 script.Comment('--- End patching dynamic partitions ---')
3294
3295 def _Compute(self):
3296 self._op_list = list()
3297
3298 def append(line):
3299 self._op_list.append(line)
3300
3301 def comment(line):
3302 self._op_list.append("# %s" % line)
3303
Peter Cai9f6c7d82020-03-01 14:43:57 +08003304 if self._build_without_vendor:
3305 comment('System-only build, keep original vendor partition')
3306 # When building without vendor, we do not want to override
3307 # any partition already existing. In this case, we can only
3308 # resize, but not remove / create / re-create any other
3309 # partition.
3310 for p, u in self._partition_updates.items():
3311 comment('Resize partition %s to %s' % (p, u.tgt_size))
3312 append('resize %s %s' % (p, u.tgt_size))
3313 return
3314
Yifan Hong10c530d2018-12-27 17:34:18 -08003315 if self._remove_all_before_apply:
3316 comment('Remove all existing dynamic partitions and groups before '
3317 'applying full OTA')
3318 append('remove_all_groups')
3319
3320 for p, u in self._partition_updates.items():
3321 if u.src_group and not u.tgt_group:
3322 append('remove %s' % p)
3323
3324 for p, u in self._partition_updates.items():
3325 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3326 comment('Move partition %s from %s to default' % (p, u.src_group))
3327 append('move %s default' % p)
3328
3329 for p, u in self._partition_updates.items():
3330 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3331 comment('Shrink partition %s from %d to %d' %
3332 (p, u.src_size, u.tgt_size))
3333 append('resize %s %s' % (p, u.tgt_size))
3334
3335 for g, u in self._group_updates.items():
3336 if u.src_size is not None and u.tgt_size is None:
3337 append('remove_group %s' % g)
3338 if (u.src_size is not None and u.tgt_size is not None and
3339 u.src_size > u.tgt_size):
3340 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3341 append('resize_group %s %d' % (g, u.tgt_size))
3342
3343 for g, u in self._group_updates.items():
3344 if u.src_size is None and u.tgt_size is not None:
3345 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3346 append('add_group %s %d' % (g, u.tgt_size))
3347 if (u.src_size is not None and u.tgt_size is not None and
3348 u.src_size < u.tgt_size):
3349 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3350 append('resize_group %s %d' % (g, u.tgt_size))
3351
3352 for p, u in self._partition_updates.items():
3353 if u.tgt_group and not u.src_group:
3354 comment('Add partition %s to group %s' % (p, u.tgt_group))
3355 append('add %s %s' % (p, u.tgt_group))
3356
3357 for p, u in self._partition_updates.items():
3358 if u.tgt_size and u.src_size < u.tgt_size:
3359 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3360 append('resize %s %d' % (p, u.tgt_size))
3361
3362 for p, u in self._partition_updates.items():
3363 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3364 comment('Move partition %s from default to %s' %
3365 (p, u.tgt_group))
3366 append('move %s %s' % (p, u.tgt_group))