blob: 7d702c651b6243dda60c43608b260d88b65d3802 [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.
393 self._device = self.GetOemProperty("ro.product.device")
394 self._fingerprint = self.CalculateFingerprint()
Daniel Normanab5acef2020-01-08 17:01:11 -0800395 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700396
397 @property
398 def is_ab(self):
399 return self._is_ab
400
401 @property
402 def device(self):
403 return self._device
404
405 @property
406 def fingerprint(self):
407 return self._fingerprint
408
409 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700410 def oem_props(self):
411 return self._oem_props
412
413 def __getitem__(self, key):
414 return self.info_dict[key]
415
416 def __setitem__(self, key, value):
417 self.info_dict[key] = value
418
419 def get(self, key, default=None):
420 return self.info_dict.get(key, default)
421
422 def items(self):
423 return self.info_dict.items()
424
Daniel Normanab5acef2020-01-08 17:01:11 -0800425 def GetPartitionBuildProp(self, prop, partition):
426 """Returns the inquired build property for the provided partition."""
427 # If provided a partition for this property, only look within that
428 # partition's build.prop.
429 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
430 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
431 else:
432 prop = prop.replace("ro.", "ro.{}.".format(partition))
433 try:
434 return self.info_dict.get("{}.build.prop".format(partition), {})[prop]
435 except KeyError:
436 raise ExternalError("couldn't find %s in %s.build.prop" %
437 (prop, partition))
438
Tao Bao1c320f82019-10-04 23:25:12 -0700439 def GetBuildProp(self, prop):
Daniel Normanab5acef2020-01-08 17:01:11 -0800440 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700441 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
442 return self._ResolveRoProductBuildProp(prop)
443
444 try:
445 return self.info_dict.get("build.prop", {})[prop]
446 except KeyError:
447 raise ExternalError("couldn't find %s in build.prop" % (prop,))
448
449 def _ResolveRoProductBuildProp(self, prop):
450 """Resolves the inquired ro.product.* build property"""
451 prop_val = self.info_dict.get("build.prop", {}).get(prop)
452 if prop_val:
453 return prop_val
454
Steven Laverdd33d752020-04-27 16:26:31 -0700455 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tao Bao1c320f82019-10-04 23:25:12 -0700456 source_order_val = self.info_dict.get("build.prop", {}).get(
457 "ro.product.property_source_order")
458 if source_order_val:
459 source_order = source_order_val.split(",")
460 else:
Steven Laverdd33d752020-04-27 16:26:31 -0700461 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700462
463 # Check that all sources in ro.product.property_source_order are valid
Steven Laverdd33d752020-04-27 16:26:31 -0700464 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700465 raise ExternalError(
466 "Invalid ro.product.property_source_order '{}'".format(source_order))
467
468 for source in source_order:
469 source_prop = prop.replace(
470 "ro.product", "ro.product.{}".format(source), 1)
471 prop_val = self.info_dict.get(
472 "{}.build.prop".format(source), {}).get(source_prop)
473 if prop_val:
474 return prop_val
475
476 raise ExternalError("couldn't resolve {}".format(prop))
477
Steven Laverdd33d752020-04-27 16:26:31 -0700478 def _GetRoProductPropsDefaultSourceOrder(self):
479 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
480 # values of these properties for each Android release.
481 android_codename = self.info_dict.get("build.prop", {}).get(
482 "ro.build.version.codename")
483 if android_codename == "REL":
484 android_version = self.info_dict.get("build.prop", {}).get(
485 "ro.build.version.release")
486 if android_version == "10":
487 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
488 # NOTE: float() conversion of android_version will have rounding error.
489 # We are checking for "9" or less, and using "< 10" is well outside of
490 # possible floating point rounding.
491 try:
492 android_version_val = float(android_version)
493 except ValueError:
494 android_version_val = 0
495 if android_version_val < 10:
496 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
497 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
498
Tao Bao1c320f82019-10-04 23:25:12 -0700499 def GetOemProperty(self, key):
500 if self.oem_props is not None and key in self.oem_props:
501 return self.oem_dicts[0][key]
502 return self.GetBuildProp(key)
503
Daniel Normanab5acef2020-01-08 17:01:11 -0800504 def GetPartitionFingerprint(self, partition):
505 return self._partition_fingerprints.get(partition, None)
506
507 def CalculatePartitionFingerprint(self, partition):
508 try:
509 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
510 except ExternalError:
511 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
512 self.GetPartitionBuildProp("ro.product.brand", partition),
513 self.GetPartitionBuildProp("ro.product.name", partition),
514 self.GetPartitionBuildProp("ro.product.device", partition),
515 self.GetPartitionBuildProp("ro.build.version.release", partition),
516 self.GetPartitionBuildProp("ro.build.id", partition),
517 self.GetPartitionBuildProp("ro.build.version.incremental", partition),
518 self.GetPartitionBuildProp("ro.build.type", partition),
519 self.GetPartitionBuildProp("ro.build.tags", partition))
520
Tao Bao1c320f82019-10-04 23:25:12 -0700521 def CalculateFingerprint(self):
522 if self.oem_props is None:
523 try:
524 return self.GetBuildProp("ro.build.fingerprint")
525 except ExternalError:
526 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
527 self.GetBuildProp("ro.product.brand"),
528 self.GetBuildProp("ro.product.name"),
529 self.GetBuildProp("ro.product.device"),
530 self.GetBuildProp("ro.build.version.release"),
531 self.GetBuildProp("ro.build.id"),
532 self.GetBuildProp("ro.build.version.incremental"),
533 self.GetBuildProp("ro.build.type"),
534 self.GetBuildProp("ro.build.tags"))
535 return "%s/%s/%s:%s" % (
536 self.GetOemProperty("ro.product.brand"),
537 self.GetOemProperty("ro.product.name"),
538 self.GetOemProperty("ro.product.device"),
539 self.GetBuildProp("ro.build.thumbprint"))
540
541 def WriteMountOemScript(self, script):
542 assert self.oem_props is not None
543 recovery_mount_options = self.info_dict.get("recovery_mount_options")
544 script.Mount("/oem", recovery_mount_options)
545
546 def WriteDeviceAssertions(self, script, oem_no_mount):
547 # Read the property directly if not using OEM properties.
548 if not self.oem_props:
549 script.AssertDevice(self.device)
550 return
551
552 # Otherwise assert OEM properties.
553 if not self.oem_dicts:
554 raise ExternalError(
555 "No OEM file provided to answer expected assertions")
556
557 for prop in self.oem_props.split():
558 values = []
559 for oem_dict in self.oem_dicts:
560 if prop in oem_dict:
561 values.append(oem_dict[prop])
562 if not values:
563 raise ExternalError(
564 "The OEM file is missing the property %s" % (prop,))
565 script.AssertOemProperty(prop, values, oem_no_mount)
566
567
Tao Bao410ad8b2018-08-24 12:08:38 -0700568def LoadInfoDict(input_file, repacking=False):
569 """Loads the key/value pairs from the given input target_files.
570
571 It reads `META/misc_info.txt` file in the target_files input, does sanity
572 checks and returns the parsed key/value pairs for to the given build. It's
573 usually called early when working on input target_files files, e.g. when
574 generating OTAs, or signing builds. Note that the function may be called
575 against an old target_files file (i.e. from past dessert releases). So the
576 property parsing needs to be backward compatible.
577
578 In a `META/misc_info.txt`, a few properties are stored as links to the files
579 in the PRODUCT_OUT directory. It works fine with the build system. However,
580 they are no longer available when (re)generating images from target_files zip.
581 When `repacking` is True, redirect these properties to the actual files in the
582 unzipped directory.
583
584 Args:
585 input_file: The input target_files file, which could be an open
586 zipfile.ZipFile instance, or a str for the dir that contains the files
587 unzipped from a target_files file.
588 repacking: Whether it's trying repack an target_files file after loading the
589 info dict (default: False). If so, it will rewrite a few loaded
590 properties (e.g. selinux_fc, root_dir) to point to the actual files in
591 target_files file. When doing repacking, `input_file` must be a dir.
592
593 Returns:
594 A dict that contains the parsed key/value pairs.
595
596 Raises:
597 AssertionError: On invalid input arguments.
598 ValueError: On malformed input values.
599 """
600 if repacking:
601 assert isinstance(input_file, str), \
602 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700603
Doug Zongkerc9253822014-02-04 12:17:58 -0800604 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700605 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800606 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800607 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700608 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800609 try:
610 with open(path) as f:
611 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700612 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800613 if e.errno == errno.ENOENT:
614 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800615
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700616 try:
Michael Runge6e836112014-04-15 17:40:21 -0700617 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700618 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700619 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700620
Tao Bao410ad8b2018-08-24 12:08:38 -0700621 if "recovery_api_version" not in d:
622 raise ValueError("Failed to find 'recovery_api_version'")
623 if "fstab_version" not in d:
624 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800625
Tao Bao410ad8b2018-08-24 12:08:38 -0700626 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700627 # "selinux_fc" properties should point to the file_contexts files
628 # (file_contexts.bin) under META/.
629 for key in d:
630 if key.endswith("selinux_fc"):
631 fc_basename = os.path.basename(d[key])
632 fc_config = os.path.join(input_file, "META", fc_basename)
633 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700634
Daniel Norman72c626f2019-05-13 15:58:14 -0700635 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700636
Tom Cherryd14b8952018-08-09 14:26:00 -0700637 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700638 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700639 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700640 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700641
David Anderson0ec64ac2019-12-06 12:21:18 -0800642 # Redirect {partition}_base_fs_file for each of the named partitions.
643 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
644 key_name = part_name + "_base_fs_file"
645 if key_name not in d:
646 continue
647 basename = os.path.basename(d[key_name])
648 base_fs_file = os.path.join(input_file, "META", basename)
649 if os.path.exists(base_fs_file):
650 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700651 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700652 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800653 "Failed to find %s base fs file: %s", part_name, base_fs_file)
654 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700655
Doug Zongker37974732010-09-16 17:44:38 -0700656 def makeint(key):
657 if key in d:
658 d[key] = int(d[key], 0)
659
660 makeint("recovery_api_version")
661 makeint("blocksize")
662 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700663 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700664 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700665 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700666 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800667 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700668
Steve Muckle53226682020-05-07 17:32:10 -0700669 boot_images = "boot.img"
670 if "boot_images" in d:
671 boot_images = d["boot_images"]
672 for b in boot_images.split():
673 makeint(b.replace(".img","_size"))
674
Tao Bao765668f2019-10-04 22:03:00 -0700675 # Load recovery fstab if applicable.
676 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800677
Tianjie Xu861f4132018-09-12 11:49:33 -0700678 # Tries to load the build props for all partitions with care_map, including
679 # system and vendor.
680 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800681 partition_prop = "{}.build.prop".format(partition)
682 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700683 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800684 # Some partition might use /<partition>/etc/build.prop as the new path.
685 # TODO: try new path first when majority of them switch to the new path.
686 if not d[partition_prop]:
687 d[partition_prop] = LoadBuildProp(
688 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700689 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800690
Tao Bao3ed35d32019-10-07 20:48:48 -0700691 # Set up the salt (based on fingerprint) that will be used when adding AVB
692 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800693 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700694 build_info = BuildInfo(d)
Daniel Normanab5acef2020-01-08 17:01:11 -0800695 for partition in PARTITIONS_WITH_CARE_MAP:
696 fingerprint = build_info.GetPartitionFingerprint(partition)
697 if fingerprint:
698 d["avb_{}_salt".format(partition)] = sha256(fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800699
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700700 return d
701
Tao Baod1de6f32017-03-01 16:38:48 -0800702
Tao Baobcd1d162017-08-26 13:10:26 -0700703def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700704 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700705 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700706 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700707 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700708 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700709 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700710
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 Xucfa86222016-03-07 16:31:19 -0800734def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
735 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700736 class Partition(object):
Yifan Hong7169f752020-04-17 10:08:10 -0700737 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700738 self.mount_point = mount_point
739 self.fs_type = fs_type
740 self.device = device
741 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700742 self.context = context
Yifan Hong7169f752020-04-17 10:08:10 -0700743 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700744
745 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800746 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700747 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700748 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700749 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700750
Tao Baod1de6f32017-03-01 16:38:48 -0800751 assert fstab_version == 2
752
753 d = {}
754 for line in data.split("\n"):
755 line = line.strip()
756 if not line or line.startswith("#"):
757 continue
758
759 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
760 pieces = line.split()
761 if len(pieces) != 5:
762 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
763
764 # Ignore entries that are managed by vold.
765 options = pieces[4]
766 if "voldmanaged=" in options:
767 continue
768
769 # It's a good line, parse it.
770 length = 0
Yifan Hong7169f752020-04-17 10:08:10 -0700771 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800772 options = options.split(",")
773 for i in options:
774 if i.startswith("length="):
775 length = int(i[7:])
Yifan Hong7169f752020-04-17 10:08:10 -0700776 elif i == "slotselect":
777 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800778 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800779 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700780 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800781
Tao Baod1de6f32017-03-01 16:38:48 -0800782 mount_flags = pieces[3]
783 # Honor the SELinux context if present.
784 context = None
785 for i in mount_flags.split(","):
786 if i.startswith("context="):
787 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800788
Tao Baod1de6f32017-03-01 16:38:48 -0800789 mount_point = pieces[1]
790 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong7169f752020-04-17 10:08:10 -0700791 device=pieces[0], length=length, context=context,
792 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800793
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700794 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700795 # system. Other areas assume system is always at "/system" so point /system
796 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700797 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800798 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700799 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700800 return d
801
802
Tao Bao765668f2019-10-04 22:03:00 -0700803def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
804 """Finds the path to recovery fstab and loads its contents."""
805 # recovery fstab is only meaningful when installing an update via recovery
806 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong7169f752020-04-17 10:08:10 -0700807 if info_dict.get('ab_update') == 'true' and \
808 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -0700809 return None
810
811 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
812 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
813 # cases, since it may load the info_dict from an old build (e.g. when
814 # generating incremental OTAs from that build).
815 system_root_image = info_dict.get('system_root_image') == 'true'
816 if info_dict.get('no_recovery') != 'true':
817 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
818 if isinstance(input_file, zipfile.ZipFile):
819 if recovery_fstab_path not in input_file.namelist():
820 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
821 else:
822 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
823 if not os.path.exists(path):
824 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
825 return LoadRecoveryFSTab(
826 read_helper, info_dict['fstab_version'], recovery_fstab_path,
827 system_root_image)
828
829 if info_dict.get('recovery_as_boot') == 'true':
830 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
831 if isinstance(input_file, zipfile.ZipFile):
832 if recovery_fstab_path not in input_file.namelist():
833 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
834 else:
835 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
836 if not os.path.exists(path):
837 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
838 return LoadRecoveryFSTab(
839 read_helper, info_dict['fstab_version'], recovery_fstab_path,
840 system_root_image)
841
842 return None
843
844
Doug Zongker37974732010-09-16 17:44:38 -0700845def DumpInfoDict(d):
846 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700847 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700848
Dan Albert8b72aef2015-03-23 19:13:21 -0700849
Daniel Norman55417142019-11-25 16:04:36 -0800850def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700851 """Merges dynamic partition info variables.
852
853 Args:
854 framework_dict: The dictionary of dynamic partition info variables from the
855 partial framework target files.
856 vendor_dict: The dictionary of dynamic partition info variables from the
857 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700858
859 Returns:
860 The merged dynamic partition info dictionary.
861 """
862 merged_dict = {}
863 # Partition groups and group sizes are defined by the vendor dict because
864 # these values may vary for each board that uses a shared system image.
865 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800866 framework_dynamic_partition_list = framework_dict.get(
867 "dynamic_partition_list", "")
868 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
869 merged_dict["dynamic_partition_list"] = ("%s %s" % (
870 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700871 for partition_group in merged_dict["super_partition_groups"].split(" "):
872 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800873 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700874 if key not in vendor_dict:
875 raise ValueError("Vendor dict does not contain required key %s." % key)
876 merged_dict[key] = vendor_dict[key]
877
878 # Set the partition group's partition list using a concatenation of the
879 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800880 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700881 merged_dict[key] = (
882 "%s %s" %
883 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530884
885 # Pick virtual ab related flags from vendor dict, if defined.
886 if "virtual_ab" in vendor_dict.keys():
887 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
888 if "virtual_ab_retrofit" in vendor_dict.keys():
889 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700890 return merged_dict
891
892
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800893def AppendAVBSigningArgs(cmd, partition):
894 """Append signing arguments for avbtool."""
895 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
896 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700897 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
898 new_key_path = os.path.join(OPTIONS.search_path, key_path)
899 if os.path.exists(new_key_path):
900 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800901 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
902 if key_path and algorithm:
903 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700904 avb_salt = OPTIONS.info_dict.get("avb_salt")
905 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700906 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700907 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800908
909
Tao Bao765668f2019-10-04 22:03:00 -0700910def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700911 """Returns the VBMeta arguments for partition.
912
913 It sets up the VBMeta argument by including the partition descriptor from the
914 given 'image', or by configuring the partition as a chained partition.
915
916 Args:
917 partition: The name of the partition (e.g. "system").
918 image: The path to the partition image.
919 info_dict: A dict returned by common.LoadInfoDict(). Will use
920 OPTIONS.info_dict if None has been given.
921
922 Returns:
923 A list of VBMeta arguments.
924 """
925 if info_dict is None:
926 info_dict = OPTIONS.info_dict
927
928 # Check if chain partition is used.
929 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +0800930 if not key_path:
931 return ["--include_descriptors_from_image", image]
932
933 # For a non-A/B device, we don't chain /recovery nor include its descriptor
934 # into vbmeta.img. The recovery image will be configured on an independent
935 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
936 # See details at
937 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -0700938 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +0800939 return []
940
941 # Otherwise chain the partition into vbmeta.
942 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
943 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -0700944
945
Tao Bao02a08592018-07-22 12:40:45 -0700946def GetAvbChainedPartitionArg(partition, info_dict, key=None):
947 """Constructs and returns the arg to build or verify a chained partition.
948
949 Args:
950 partition: The partition name.
951 info_dict: The info dict to look up the key info and rollback index
952 location.
953 key: The key to be used for building or verifying the partition. Defaults to
954 the key listed in info_dict.
955
956 Returns:
957 A string of form "partition:rollback_index_location:key" that can be used to
958 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700959 """
960 if key is None:
961 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700962 if key and not os.path.exists(key) and OPTIONS.search_path:
963 new_key_path = os.path.join(OPTIONS.search_path, key)
964 if os.path.exists(new_key_path):
965 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700966 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700967 rollback_index_location = info_dict[
968 "avb_" + partition + "_rollback_index_location"]
969 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
970
971
Daniel Norman276f0622019-07-26 14:13:51 -0700972def BuildVBMeta(image_path, partitions, name, needed_partitions):
973 """Creates a VBMeta image.
974
975 It generates the requested VBMeta image. The requested image could be for
976 top-level or chained VBMeta image, which is determined based on the name.
977
978 Args:
979 image_path: The output path for the new VBMeta image.
980 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chend9edddd2020-04-27 18:36:36 -0700981 values. Only valid partition names are accepted, as partitions listed
982 in common.AVB_PARTITIONS and custom partitions listed in
983 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -0700984 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
985 needed_partitions: Partitions whose descriptors should be included into the
986 generated VBMeta image.
987
988 Raises:
989 AssertionError: On invalid input args.
990 """
991 avbtool = OPTIONS.info_dict["avb_avbtool"]
992 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
993 AppendAVBSigningArgs(cmd, name)
994
Hongguang Chend9edddd2020-04-27 18:36:36 -0700995 custom_partitions = OPTIONS.info_dict.get(
996 "avb_custom_images_partition_list", "").strip().split()
997
Daniel Norman276f0622019-07-26 14:13:51 -0700998 for partition, path in partitions.items():
999 if partition not in needed_partitions:
1000 continue
1001 assert (partition in AVB_PARTITIONS or
Hongguang Chend9edddd2020-04-27 18:36:36 -07001002 partition in AVB_VBMETA_PARTITIONS or
1003 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001004 'Unknown partition: {}'.format(partition)
1005 assert os.path.exists(path), \
1006 'Failed to find {} for {}'.format(path, partition)
1007 cmd.extend(GetAvbPartitionArg(partition, path))
1008
1009 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1010 if args and args.strip():
1011 split_args = shlex.split(args)
1012 for index, arg in enumerate(split_args[:-1]):
1013 # Sanity check that the image file exists. Some images might be defined
1014 # as a path relative to source tree, which may not be available at the
1015 # same location when running this script (we have the input target_files
1016 # zip only). For such cases, we additionally scan other locations (e.g.
1017 # IMAGES/, RADIO/, etc) before bailing out.
1018 if arg == '--include_descriptors_from_image':
1019 image_path = split_args[index + 1]
1020 if os.path.exists(image_path):
1021 continue
1022 found = False
1023 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1024 alt_path = os.path.join(
1025 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
1026 if os.path.exists(alt_path):
1027 split_args[index + 1] = alt_path
1028 found = True
1029 break
1030 assert found, 'Failed to find {}'.format(image_path)
1031 cmd.extend(split_args)
1032
1033 RunAndCheckOutput(cmd)
1034
Dan Austin52903642019-12-12 15:44:00 -08001035 if OPTIONS.aftl_server is not None:
1036 # Ensure the other AFTL parameters are set as well.
1037 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1038 assert OPTIONS.aftl_manufacturer_key_path is not None, 'No AFTL manufacturer key provided.'
1039 assert OPTIONS.aftl_signer_helper is not None, 'No AFTL signer helper provided.'
1040 # AFTL inclusion proof generation code will go here.
Daniel Norman276f0622019-07-26 14:13:51 -07001041
Steve Mucklee1b10862019-07-10 10:49:37 -07001042def _MakeRamdisk(sourcedir, fs_config_file=None):
1043 ramdisk_img = tempfile.NamedTemporaryFile()
1044
1045 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1046 cmd = ["mkbootfs", "-f", fs_config_file,
1047 os.path.join(sourcedir, "RAMDISK")]
1048 else:
1049 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1050 p1 = Run(cmd, stdout=subprocess.PIPE)
1051 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1052
1053 p2.wait()
1054 p1.wait()
1055 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1056 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1057
1058 return ramdisk_img
1059
1060
Steve Mucklef83e3c32020-04-08 18:27:00 -07001061def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001062 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001063 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001064
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001065 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001066 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1067 we are building a two-step special image (i.e. building a recovery image to
1068 be loaded into /boot in two-step OTAs).
1069
1070 Return the image data, or None if sourcedir does not appear to contains files
1071 for building the requested image.
1072 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001073
Steve Mucklef83e3c32020-04-08 18:27:00 -07001074 # "boot" or "recovery", without extension.
1075 partition_name = os.path.basename(sourcedir).lower()
1076
1077 if partition_name == "recovery":
1078 kernel = "kernel"
1079 else:
1080 kernel = image_name.replace("boot", "kernel")
1081 kernel = kernel.replace(".img","")
1082 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001083 return None
1084
1085 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001086 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001087
Doug Zongkerd5131602012-08-02 14:46:42 -07001088 if info_dict is None:
1089 info_dict = OPTIONS.info_dict
1090
Doug Zongkereef39442009-04-02 12:14:19 -07001091 img = tempfile.NamedTemporaryFile()
1092
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001093 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001094 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001095
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001096 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1097 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1098
Steve Mucklef83e3c32020-04-08 18:27:00 -07001099 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001100
Benoit Fradina45a8682014-07-14 21:00:43 +02001101 fn = os.path.join(sourcedir, "second")
1102 if os.access(fn, os.F_OK):
1103 cmd.append("--second")
1104 cmd.append(fn)
1105
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001106 fn = os.path.join(sourcedir, "dtb")
1107 if os.access(fn, os.F_OK):
1108 cmd.append("--dtb")
1109 cmd.append(fn)
1110
Doug Zongker171f1cd2009-06-15 22:36:37 -07001111 fn = os.path.join(sourcedir, "cmdline")
1112 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001113 cmd.append("--cmdline")
1114 cmd.append(open(fn).read().rstrip("\n"))
1115
1116 fn = os.path.join(sourcedir, "base")
1117 if os.access(fn, os.F_OK):
1118 cmd.append("--base")
1119 cmd.append(open(fn).read().rstrip("\n"))
1120
Ying Wang4de6b5b2010-08-25 14:29:34 -07001121 fn = os.path.join(sourcedir, "pagesize")
1122 if os.access(fn, os.F_OK):
1123 cmd.append("--pagesize")
1124 cmd.append(open(fn).read().rstrip("\n"))
1125
Steve Muckle759d0c82020-03-16 19:13:46 -07001126 if partition_name == "recovery":
1127 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddye4d5d562020-05-04 19:40:16 +05301128 if not args:
1129 # Fall back to "mkbootimg_args" for recovery image
1130 # in case "recovery_mkbootimg_args" is not set.
1131 args = info_dict.get("mkbootimg_args")
Steve Muckle759d0c82020-03-16 19:13:46 -07001132 else:
1133 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001134 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001135 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001136
Tao Bao76def242017-11-21 09:25:31 -08001137 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001138 if args and args.strip():
1139 cmd.extend(shlex.split(args))
1140
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001141 if has_ramdisk:
1142 cmd.extend(["--ramdisk", ramdisk_img.name])
1143
Tao Baod95e9fd2015-03-29 23:07:41 -07001144 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001145 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001146 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001147 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001148 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001149 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001150
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001151 if partition_name == "recovery":
1152 if info_dict.get("include_recovery_dtbo") == "true":
1153 fn = os.path.join(sourcedir, "recovery_dtbo")
1154 cmd.extend(["--recovery_dtbo", fn])
1155 if info_dict.get("include_recovery_acpio") == "true":
1156 fn = os.path.join(sourcedir, "recovery_acpio")
1157 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001158
Tao Bao986ee862018-10-04 15:46:16 -07001159 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001160
Tao Bao76def242017-11-21 09:25:31 -08001161 if (info_dict.get("boot_signer") == "true" and
1162 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001163 # Hard-code the path as "/boot" for two-step special recovery image (which
1164 # will be loaded into /boot during the two-step OTA).
1165 if two_step_image:
1166 path = "/boot"
1167 else:
Tao Baobf70c312017-07-11 17:27:55 -07001168 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001169 cmd = [OPTIONS.boot_signer_path]
1170 cmd.extend(OPTIONS.boot_signer_args)
1171 cmd.extend([path, img.name,
1172 info_dict["verity_key"] + ".pk8",
1173 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001174 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001175
Tao Baod95e9fd2015-03-29 23:07:41 -07001176 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001177 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001178 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001179 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001180 # We have switched from the prebuilt futility binary to using the tool
1181 # (futility-host) built from the source. Override the setting in the old
1182 # TF.zip.
1183 futility = info_dict["futility"]
1184 if futility.startswith("prebuilts/"):
1185 futility = "futility-host"
1186 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001187 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001188 info_dict["vboot_key"] + ".vbprivk",
1189 info_dict["vboot_subkey"] + ".vbprivk",
1190 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001191 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001192 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001193
Tao Baof3282b42015-04-01 11:21:55 -07001194 # Clean up the temp files.
1195 img_unsigned.close()
1196 img_keyblock.close()
1197
David Zeuthen8fecb282017-12-01 16:24:01 -05001198 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001199 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001200 avbtool = info_dict["avb_avbtool"]
Steve Muckle53226682020-05-07 17:32:10 -07001201 if partition_name == "recovery":
1202 part_size = info_dict["recovery_size"]
1203 else:
1204 part_size = info_dict[image_name.replace(".img","_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001205 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001206 "--partition_size", str(part_size), "--partition_name",
1207 partition_name]
1208 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001209 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001210 if args and args.strip():
1211 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001212 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001213
1214 img.seek(os.SEEK_SET, 0)
1215 data = img.read()
1216
1217 if has_ramdisk:
1218 ramdisk_img.close()
1219 img.close()
1220
1221 return data
1222
1223
Doug Zongkerd5131602012-08-02 14:46:42 -07001224def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001225 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001226 """Return a File object with the desired bootable image.
1227
1228 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1229 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1230 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001231
Doug Zongker55d93282011-01-25 17:03:34 -08001232 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1233 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001234 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001235 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001236
1237 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1238 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001239 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001240 return File.FromLocalFile(name, prebuilt_path)
1241
Tao Bao32fcdab2018-10-12 10:30:39 -07001242 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001243
1244 if info_dict is None:
1245 info_dict = OPTIONS.info_dict
1246
1247 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001248 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1249 # for recovery.
1250 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1251 prebuilt_name != "boot.img" or
1252 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001253
Doug Zongker6f1d0312014-08-22 08:07:12 -07001254 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Mucklef83e3c32020-04-08 18:27:00 -07001255 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001256 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001257 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001258 if data:
1259 return File(name, data)
1260 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001261
Doug Zongkereef39442009-04-02 12:14:19 -07001262
Steve Mucklee1b10862019-07-10 10:49:37 -07001263def _BuildVendorBootImage(sourcedir, info_dict=None):
1264 """Build a vendor boot image from the specified sourcedir.
1265
1266 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1267 turn them into a vendor boot image.
1268
1269 Return the image data, or None if sourcedir does not appear to contains files
1270 for building the requested image.
1271 """
1272
1273 if info_dict is None:
1274 info_dict = OPTIONS.info_dict
1275
1276 img = tempfile.NamedTemporaryFile()
1277
1278 ramdisk_img = _MakeRamdisk(sourcedir)
1279
1280 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1281 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1282
1283 cmd = [mkbootimg]
1284
1285 fn = os.path.join(sourcedir, "dtb")
1286 if os.access(fn, os.F_OK):
1287 cmd.append("--dtb")
1288 cmd.append(fn)
1289
1290 fn = os.path.join(sourcedir, "vendor_cmdline")
1291 if os.access(fn, os.F_OK):
1292 cmd.append("--vendor_cmdline")
1293 cmd.append(open(fn).read().rstrip("\n"))
1294
1295 fn = os.path.join(sourcedir, "base")
1296 if os.access(fn, os.F_OK):
1297 cmd.append("--base")
1298 cmd.append(open(fn).read().rstrip("\n"))
1299
1300 fn = os.path.join(sourcedir, "pagesize")
1301 if os.access(fn, os.F_OK):
1302 cmd.append("--pagesize")
1303 cmd.append(open(fn).read().rstrip("\n"))
1304
1305 args = info_dict.get("mkbootimg_args")
1306 if args and args.strip():
1307 cmd.extend(shlex.split(args))
1308
1309 args = info_dict.get("mkbootimg_version_args")
1310 if args and args.strip():
1311 cmd.extend(shlex.split(args))
1312
1313 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1314 cmd.extend(["--vendor_boot", img.name])
1315
1316 RunAndCheckOutput(cmd)
1317
1318 # AVB: if enabled, calculate and add hash.
1319 if info_dict.get("avb_enable") == "true":
1320 avbtool = info_dict["avb_avbtool"]
1321 part_size = info_dict["vendor_boot_size"]
1322 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001323 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001324 AppendAVBSigningArgs(cmd, "vendor_boot")
1325 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1326 if args and args.strip():
1327 cmd.extend(shlex.split(args))
1328 RunAndCheckOutput(cmd)
1329
1330 img.seek(os.SEEK_SET, 0)
1331 data = img.read()
1332
1333 ramdisk_img.close()
1334 img.close()
1335
1336 return data
1337
1338
1339def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1340 info_dict=None):
1341 """Return a File object with the desired vendor boot image.
1342
1343 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1344 the source files in 'unpack_dir'/'tree_subdir'."""
1345
1346 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1347 if os.path.exists(prebuilt_path):
1348 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1349 return File.FromLocalFile(name, prebuilt_path)
1350
1351 logger.info("building image from target_files %s...", tree_subdir)
1352
1353 if info_dict is None:
1354 info_dict = OPTIONS.info_dict
1355
1356 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1357 if data:
1358 return File(name, data)
1359 return None
1360
1361
Narayan Kamatha07bf042017-08-14 14:49:21 +01001362def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001363 """Gunzips the given gzip compressed file to a given output file."""
1364 with gzip.open(in_filename, "rb") as in_file, \
1365 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001366 shutil.copyfileobj(in_file, out_file)
1367
1368
Tao Bao0ff15de2019-03-20 11:26:06 -07001369def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001370 """Unzips the archive to the given directory.
1371
1372 Args:
1373 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001374 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001375 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1376 archvie. Non-matching patterns will be filtered out. If there's no match
1377 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001378 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001379 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001380 if patterns is not None:
1381 # Filter out non-matching patterns. unzip will complain otherwise.
1382 with zipfile.ZipFile(filename) as input_zip:
1383 names = input_zip.namelist()
1384 filtered = [
1385 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1386
1387 # There isn't any matching files. Don't unzip anything.
1388 if not filtered:
1389 return
1390 cmd.extend(filtered)
1391
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001392 RunAndCheckOutput(cmd)
1393
1394
Doug Zongker75f17362009-12-08 13:46:44 -08001395def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001396 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001397
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001398 Args:
1399 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1400 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1401
1402 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1403 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001404
Tao Bao1c830bf2017-12-25 10:43:47 -08001405 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001406 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001407 """
Doug Zongkereef39442009-04-02 12:14:19 -07001408
Tao Bao1c830bf2017-12-25 10:43:47 -08001409 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001410 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1411 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001412 UnzipToDir(m.group(1), tmp, pattern)
1413 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001414 filename = m.group(1)
1415 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001416 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001417
Tao Baodba59ee2018-01-09 13:21:02 -08001418 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001419
1420
Yifan Hong8a66a712019-04-04 15:37:57 -07001421def GetUserImage(which, tmpdir, input_zip,
1422 info_dict=None,
1423 allow_shared_blocks=None,
1424 hashtree_info_generator=None,
1425 reset_file_map=False):
1426 """Returns an Image object suitable for passing to BlockImageDiff.
1427
1428 This function loads the specified image from the given path. If the specified
1429 image is sparse, it also performs additional processing for OTA purpose. For
1430 example, it always adds block 0 to clobbered blocks list. It also detects
1431 files that cannot be reconstructed from the block list, for whom we should
1432 avoid applying imgdiff.
1433
1434 Args:
1435 which: The partition name.
1436 tmpdir: The directory that contains the prebuilt image and block map file.
1437 input_zip: The target-files ZIP archive.
1438 info_dict: The dict to be looked up for relevant info.
1439 allow_shared_blocks: If image is sparse, whether having shared blocks is
1440 allowed. If none, it is looked up from info_dict.
1441 hashtree_info_generator: If present and image is sparse, generates the
1442 hashtree_info for this sparse image.
1443 reset_file_map: If true and image is sparse, reset file map before returning
1444 the image.
1445 Returns:
1446 A Image object. If it is a sparse image and reset_file_map is False, the
1447 image will have file_map info loaded.
1448 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001449 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001450 info_dict = LoadInfoDict(input_zip)
1451
1452 is_sparse = info_dict.get("extfs_sparse_flag")
1453
1454 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1455 # shared blocks (i.e. some blocks will show up in multiple files' block
1456 # list). We can only allocate such shared blocks to the first "owner", and
1457 # disable imgdiff for all later occurrences.
1458 if allow_shared_blocks is None:
1459 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1460
1461 if is_sparse:
1462 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1463 hashtree_info_generator)
1464 if reset_file_map:
1465 img.ResetFileMap()
1466 return img
1467 else:
1468 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1469
1470
1471def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1472 """Returns a Image object suitable for passing to BlockImageDiff.
1473
1474 This function loads the specified non-sparse image from the given path.
1475
1476 Args:
1477 which: The partition name.
1478 tmpdir: The directory that contains the prebuilt image and block map file.
1479 Returns:
1480 A Image object.
1481 """
1482 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1483 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1484
1485 # The image and map files must have been created prior to calling
1486 # ota_from_target_files.py (since LMP).
1487 assert os.path.exists(path) and os.path.exists(mappath)
1488
Tianjie Xu41976c72019-07-03 13:57:01 -07001489 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1490
Yifan Hong8a66a712019-04-04 15:37:57 -07001491
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001492def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1493 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001494 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1495
1496 This function loads the specified sparse image from the given path, and
1497 performs additional processing for OTA purpose. For example, it always adds
1498 block 0 to clobbered blocks list. It also detects files that cannot be
1499 reconstructed from the block list, for whom we should avoid applying imgdiff.
1500
1501 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001502 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001503 tmpdir: The directory that contains the prebuilt image and block map file.
1504 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001505 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001506 hashtree_info_generator: If present, generates the hashtree_info for this
1507 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001508 Returns:
1509 A SparseImage object, with file_map info loaded.
1510 """
Tao Baoc765cca2018-01-31 17:32:40 -08001511 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1512 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1513
1514 # The image and map files must have been created prior to calling
1515 # ota_from_target_files.py (since LMP).
1516 assert os.path.exists(path) and os.path.exists(mappath)
1517
1518 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1519 # it to clobbered_blocks so that it will be written to the target
1520 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1521 clobbered_blocks = "0"
1522
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001523 image = sparse_img.SparseImage(
1524 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1525 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001526
1527 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1528 # if they contain all zeros. We can't reconstruct such a file from its block
1529 # list. Tag such entries accordingly. (Bug: 65213616)
1530 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001531 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001532 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001533 continue
1534
Tom Cherryd14b8952018-08-09 14:26:00 -07001535 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1536 # filename listed in system.map may contain an additional leading slash
1537 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1538 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001539 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001540
Tom Cherryd14b8952018-08-09 14:26:00 -07001541 # Special handling another case, where files not under /system
1542 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001543 if which == 'system' and not arcname.startswith('SYSTEM'):
1544 arcname = 'ROOT/' + arcname
1545
1546 assert arcname in input_zip.namelist(), \
1547 "Failed to find the ZIP entry for {}".format(entry)
1548
Tao Baoc765cca2018-01-31 17:32:40 -08001549 info = input_zip.getinfo(arcname)
1550 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001551
1552 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001553 # image, check the original block list to determine its completeness. Note
1554 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001555 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001556 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001557
Tao Baoc765cca2018-01-31 17:32:40 -08001558 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1559 ranges.extra['incomplete'] = True
1560
1561 return image
1562
1563
Doug Zongkereef39442009-04-02 12:14:19 -07001564def GetKeyPasswords(keylist):
1565 """Given a list of keys, prompt the user to enter passwords for
1566 those which require them. Return a {key: password} dict. password
1567 will be None if the key has no password."""
1568
Doug Zongker8ce7c252009-05-22 13:34:54 -07001569 no_passwords = []
1570 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001571 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001572 devnull = open("/dev/null", "w+b")
1573 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001574 # We don't need a password for things that aren't really keys.
1575 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001576 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001577 continue
1578
T.R. Fullhart37e10522013-03-18 10:31:26 -07001579 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001580 "-inform", "DER", "-nocrypt"],
1581 stdin=devnull.fileno(),
1582 stdout=devnull.fileno(),
1583 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001584 p.communicate()
1585 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001586 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001587 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001588 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001589 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1590 "-inform", "DER", "-passin", "pass:"],
1591 stdin=devnull.fileno(),
1592 stdout=devnull.fileno(),
1593 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001594 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001595 if p.returncode == 0:
1596 # Encrypted key with empty string as password.
1597 key_passwords[k] = ''
1598 elif stderr.startswith('Error decrypting key'):
1599 # Definitely encrypted key.
1600 # It would have said "Error reading key" if it didn't parse correctly.
1601 need_passwords.append(k)
1602 else:
1603 # Potentially, a type of key that openssl doesn't understand.
1604 # We'll let the routines in signapk.jar handle it.
1605 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001606 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001607
T.R. Fullhart37e10522013-03-18 10:31:26 -07001608 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001609 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001610 return key_passwords
1611
1612
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001613def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001614 """Gets the minSdkVersion declared in the APK.
1615
changho.shin0f125362019-07-08 10:59:00 +09001616 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001617 This can be both a decimal number (API Level) or a codename.
1618
1619 Args:
1620 apk_name: The APK filename.
1621
1622 Returns:
1623 The parsed SDK version string.
1624
1625 Raises:
1626 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001627 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001628 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001629 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001630 stderr=subprocess.PIPE)
1631 stdoutdata, stderrdata = proc.communicate()
1632 if proc.returncode != 0:
1633 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001634 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001635 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001636
Tao Baof47bf0f2018-03-21 23:28:51 -07001637 for line in stdoutdata.split("\n"):
1638 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001639 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1640 if m:
1641 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001642 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001643
1644
1645def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001646 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001647
Tao Baof47bf0f2018-03-21 23:28:51 -07001648 If minSdkVersion is set to a codename, it is translated to a number using the
1649 provided map.
1650
1651 Args:
1652 apk_name: The APK filename.
1653
1654 Returns:
1655 The parsed SDK version number.
1656
1657 Raises:
1658 ExternalError: On failing to get the min SDK version number.
1659 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001660 version = GetMinSdkVersion(apk_name)
1661 try:
1662 return int(version)
1663 except ValueError:
1664 # Not a decimal number. Codename?
1665 if version in codename_to_api_level_map:
1666 return codename_to_api_level_map[version]
1667 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001668 raise ExternalError(
1669 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1670 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001671
1672
1673def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001674 codename_to_api_level_map=None, whole_file=False,
1675 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001676 """Sign the input_name zip/jar/apk, producing output_name. Use the
1677 given key and password (the latter may be None if the key does not
1678 have a password.
1679
Doug Zongker951495f2009-08-14 12:44:19 -07001680 If whole_file is true, use the "-w" option to SignApk to embed a
1681 signature that covers the whole file in the archive comment of the
1682 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001683
1684 min_api_level is the API Level (int) of the oldest platform this file may end
1685 up on. If not specified for an APK, the API Level is obtained by interpreting
1686 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1687
1688 codename_to_api_level_map is needed to translate the codename which may be
1689 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001690
1691 Caller may optionally specify extra args to be passed to SignApk, which
1692 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001693 """
Tao Bao76def242017-11-21 09:25:31 -08001694 if codename_to_api_level_map is None:
1695 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001696 if extra_signapk_args is None:
1697 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001698
Alex Klyubin9667b182015-12-10 13:38:50 -08001699 java_library_path = os.path.join(
1700 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1701
Tao Baoe95540e2016-11-08 12:08:53 -08001702 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1703 ["-Djava.library.path=" + java_library_path,
1704 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001705 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001706 if whole_file:
1707 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001708
1709 min_sdk_version = min_api_level
1710 if min_sdk_version is None:
1711 if not whole_file:
1712 min_sdk_version = GetMinSdkVersionInt(
1713 input_name, codename_to_api_level_map)
1714 if min_sdk_version is not None:
1715 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1716
T.R. Fullhart37e10522013-03-18 10:31:26 -07001717 cmd.extend([key + OPTIONS.public_key_suffix,
1718 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001719 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001720
Tao Bao73dd4f42018-10-04 16:25:33 -07001721 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001722 if password is not None:
1723 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001724 stdoutdata, _ = proc.communicate(password)
1725 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001726 raise ExternalError(
1727 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001728 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001729
Doug Zongkereef39442009-04-02 12:14:19 -07001730
Doug Zongker37974732010-09-16 17:44:38 -07001731def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001732 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001733
Tao Bao9dd909e2017-11-14 11:27:32 -08001734 For non-AVB images, raise exception if the data is too big. Print a warning
1735 if the data is nearing the maximum size.
1736
1737 For AVB images, the actual image size should be identical to the limit.
1738
1739 Args:
1740 data: A string that contains all the data for the partition.
1741 target: The partition name. The ".img" suffix is optional.
1742 info_dict: The dict to be looked up for relevant info.
1743 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001744 if target.endswith(".img"):
1745 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001746 mount_point = "/" + target
1747
Ying Wangf8824af2014-06-03 14:07:27 -07001748 fs_type = None
1749 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001750 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001751 if mount_point == "/userdata":
1752 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001753 p = info_dict["fstab"][mount_point]
1754 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001755 device = p.device
1756 if "/" in device:
1757 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001758 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001759 if not fs_type or not limit:
1760 return
Doug Zongkereef39442009-04-02 12:14:19 -07001761
Andrew Boie0f9aec82012-02-14 09:32:52 -08001762 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001763 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1764 # path.
1765 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1766 if size != limit:
1767 raise ExternalError(
1768 "Mismatching image size for %s: expected %d actual %d" % (
1769 target, limit, size))
1770 else:
1771 pct = float(size) * 100.0 / limit
1772 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1773 if pct >= 99.0:
1774 raise ExternalError(msg)
1775 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001776 logger.warning("\n WARNING: %s\n", msg)
1777 else:
1778 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001779
1780
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001781def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001782 """Parses the APK certs info from a given target-files zip.
1783
1784 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1785 tuple with the following elements: (1) a dictionary that maps packages to
1786 certs (based on the "certificate" and "private_key" attributes in the file;
1787 (2) a string representing the extension of compressed APKs in the target files
1788 (e.g ".gz", ".bro").
1789
1790 Args:
1791 tf_zip: The input target_files ZipFile (already open).
1792
1793 Returns:
1794 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1795 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1796 no compressed APKs.
1797 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001798 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001799 compressed_extension = None
1800
Tao Bao0f990332017-09-08 19:02:54 -07001801 # META/apkcerts.txt contains the info for _all_ the packages known at build
1802 # time. Filter out the ones that are not installed.
1803 installed_files = set()
1804 for name in tf_zip.namelist():
1805 basename = os.path.basename(name)
1806 if basename:
1807 installed_files.add(basename)
1808
Tao Baoda30cfa2017-12-01 16:19:46 -08001809 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001810 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001811 if not line:
1812 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001813 m = re.match(
1814 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham96c9e6e2020-04-03 15:36:23 -07001815 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1816 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001817 line)
1818 if not m:
1819 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001820
Tao Bao818ddf52018-01-05 11:17:34 -08001821 matches = m.groupdict()
1822 cert = matches["CERT"]
1823 privkey = matches["PRIVKEY"]
1824 name = matches["NAME"]
1825 this_compressed_extension = matches["COMPRESSED"]
1826
1827 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1828 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1829 if cert in SPECIAL_CERT_STRINGS and not privkey:
1830 certmap[name] = cert
1831 elif (cert.endswith(OPTIONS.public_key_suffix) and
1832 privkey.endswith(OPTIONS.private_key_suffix) and
1833 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1834 certmap[name] = cert[:-public_key_suffix_len]
1835 else:
1836 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1837
1838 if not this_compressed_extension:
1839 continue
1840
1841 # Only count the installed files.
1842 filename = name + '.' + this_compressed_extension
1843 if filename not in installed_files:
1844 continue
1845
1846 # Make sure that all the values in the compression map have the same
1847 # extension. We don't support multiple compression methods in the same
1848 # system image.
1849 if compressed_extension:
1850 if this_compressed_extension != compressed_extension:
1851 raise ValueError(
1852 "Multiple compressed extensions: {} vs {}".format(
1853 compressed_extension, this_compressed_extension))
1854 else:
1855 compressed_extension = this_compressed_extension
1856
1857 return (certmap,
1858 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001859
1860
Doug Zongkereef39442009-04-02 12:14:19 -07001861COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001862Global options
1863
1864 -p (--path) <dir>
1865 Prepend <dir>/bin to the list of places to search for binaries run by this
1866 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001867
Doug Zongker05d3dea2009-06-22 11:32:31 -07001868 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001869 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001870
Tao Bao30df8b42018-04-23 15:32:53 -07001871 -x (--extra) <key=value>
1872 Add a key/value pair to the 'extras' dict, which device-specific extension
1873 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001874
Doug Zongkereef39442009-04-02 12:14:19 -07001875 -v (--verbose)
1876 Show command lines being executed.
1877
1878 -h (--help)
1879 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001880
1881 --logfile <file>
1882 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001883"""
1884
1885def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001886 print(docstring.rstrip("\n"))
1887 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001888
1889
1890def ParseOptions(argv,
1891 docstring,
1892 extra_opts="", extra_long_opts=(),
1893 extra_option_handler=None):
1894 """Parse the options in argv and return any arguments that aren't
1895 flags. docstring is the calling module's docstring, to be displayed
1896 for errors and -h. extra_opts and extra_long_opts are for flags
1897 defined by the caller, which are processed by passing them to
1898 extra_option_handler."""
1899
1900 try:
1901 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001902 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001903 ["help", "verbose", "path=", "signapk_path=",
1904 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08001905 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001906 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1907 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Dan Austin52903642019-12-12 15:44:00 -08001908 "extra=", "logfile=", "aftl_server=", "aftl_key_path=",
1909 "aftl_manufacturer_key_path=", "aftl_signer_helper="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001910 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001911 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001912 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001913 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001914 sys.exit(2)
1915
Doug Zongkereef39442009-04-02 12:14:19 -07001916 for o, a in opts:
1917 if o in ("-h", "--help"):
1918 Usage(docstring)
1919 sys.exit()
1920 elif o in ("-v", "--verbose"):
1921 OPTIONS.verbose = True
1922 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001923 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001924 elif o in ("--signapk_path",):
1925 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001926 elif o in ("--signapk_shared_library_path",):
1927 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001928 elif o in ("--extra_signapk_args",):
1929 OPTIONS.extra_signapk_args = shlex.split(a)
1930 elif o in ("--java_path",):
1931 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001932 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001933 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08001934 elif o in ("--android_jar_path",):
1935 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001936 elif o in ("--public_key_suffix",):
1937 OPTIONS.public_key_suffix = a
1938 elif o in ("--private_key_suffix",):
1939 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001940 elif o in ("--boot_signer_path",):
1941 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001942 elif o in ("--boot_signer_args",):
1943 OPTIONS.boot_signer_args = shlex.split(a)
1944 elif o in ("--verity_signer_path",):
1945 OPTIONS.verity_signer_path = a
1946 elif o in ("--verity_signer_args",):
1947 OPTIONS.verity_signer_args = shlex.split(a)
Dan Austin52903642019-12-12 15:44:00 -08001948 elif o in ("--aftl_server",):
1949 OPTIONS.aftl_server = a
1950 elif o in ("--aftl_key_path",):
1951 OPTIONS.aftl_key_path = a
1952 elif o in ("--aftl_manufacturer_key_path",):
1953 OPTIONS.aftl_manufacturer_key_path = a
1954 elif o in ("--aftl_signer_helper",):
1955 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07001956 elif o in ("-s", "--device_specific"):
1957 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001958 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001959 key, value = a.split("=", 1)
1960 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001961 elif o in ("--logfile",):
1962 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001963 else:
1964 if extra_option_handler is None or not extra_option_handler(o, a):
1965 assert False, "unknown option \"%s\"" % (o,)
1966
Doug Zongker85448772014-09-09 14:59:20 -07001967 if OPTIONS.search_path:
1968 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1969 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001970
1971 return args
1972
1973
Tao Bao4c851b12016-09-19 13:54:38 -07001974def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001975 """Make a temp file and add it to the list of things to be deleted
1976 when Cleanup() is called. Return the filename."""
1977 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1978 os.close(fd)
1979 OPTIONS.tempfiles.append(fn)
1980 return fn
1981
1982
Tao Bao1c830bf2017-12-25 10:43:47 -08001983def MakeTempDir(prefix='tmp', suffix=''):
1984 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1985
1986 Returns:
1987 The absolute pathname of the new directory.
1988 """
1989 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1990 OPTIONS.tempfiles.append(dir_name)
1991 return dir_name
1992
1993
Doug Zongkereef39442009-04-02 12:14:19 -07001994def Cleanup():
1995 for i in OPTIONS.tempfiles:
1996 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001997 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001998 else:
1999 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002000 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002001
2002
2003class PasswordManager(object):
2004 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002005 self.editor = os.getenv("EDITOR")
2006 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002007
2008 def GetPasswords(self, items):
2009 """Get passwords corresponding to each string in 'items',
2010 returning a dict. (The dict may have keys in addition to the
2011 values in 'items'.)
2012
2013 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2014 user edit that file to add more needed passwords. If no editor is
2015 available, or $ANDROID_PW_FILE isn't define, prompts the user
2016 interactively in the ordinary way.
2017 """
2018
2019 current = self.ReadFile()
2020
2021 first = True
2022 while True:
2023 missing = []
2024 for i in items:
2025 if i not in current or not current[i]:
2026 missing.append(i)
2027 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002028 if not missing:
2029 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002030
2031 for i in missing:
2032 current[i] = ""
2033
2034 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002035 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002036 if sys.version_info[0] >= 3:
2037 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002038 answer = raw_input("try to edit again? [y]> ").strip()
2039 if answer and answer[0] not in 'yY':
2040 raise RuntimeError("key passwords unavailable")
2041 first = False
2042
2043 current = self.UpdateAndReadFile(current)
2044
Dan Albert8b72aef2015-03-23 19:13:21 -07002045 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002046 """Prompt the user to enter a value (password) for each key in
2047 'current' whose value is fales. Returns a new dict with all the
2048 values.
2049 """
2050 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002051 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002052 if v:
2053 result[k] = v
2054 else:
2055 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002056 result[k] = getpass.getpass(
2057 "Enter password for %s key> " % k).strip()
2058 if result[k]:
2059 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002060 return result
2061
2062 def UpdateAndReadFile(self, current):
2063 if not self.editor or not self.pwfile:
2064 return self.PromptResult(current)
2065
2066 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002067 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002068 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2069 f.write("# (Additional spaces are harmless.)\n\n")
2070
2071 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002072 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002073 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002074 f.write("[[[ %s ]]] %s\n" % (v, k))
2075 if not v and first_line is None:
2076 # position cursor on first line with no password.
2077 first_line = i + 4
2078 f.close()
2079
Tao Bao986ee862018-10-04 15:46:16 -07002080 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002081
2082 return self.ReadFile()
2083
2084 def ReadFile(self):
2085 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002086 if self.pwfile is None:
2087 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002088 try:
2089 f = open(self.pwfile, "r")
2090 for line in f:
2091 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002092 if not line or line[0] == '#':
2093 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002094 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2095 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002096 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002097 else:
2098 result[m.group(2)] = m.group(1)
2099 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002100 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002101 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002102 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002103 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002104
2105
Dan Albert8e0178d2015-01-27 15:53:15 -08002106def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2107 compress_type=None):
2108 import datetime
2109
2110 # http://b/18015246
2111 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2112 # for files larger than 2GiB. We can work around this by adjusting their
2113 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2114 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2115 # it isn't clear to me exactly what circumstances cause this).
2116 # `zipfile.write()` must be used directly to work around this.
2117 #
2118 # This mess can be avoided if we port to python3.
2119 saved_zip64_limit = zipfile.ZIP64_LIMIT
2120 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2121
2122 if compress_type is None:
2123 compress_type = zip_file.compression
2124 if arcname is None:
2125 arcname = filename
2126
2127 saved_stat = os.stat(filename)
2128
2129 try:
2130 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2131 # file to be zipped and reset it when we're done.
2132 os.chmod(filename, perms)
2133
2134 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002135 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2136 # intentional. zip stores datetimes in local time without a time zone
2137 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2138 # in the zip archive.
2139 local_epoch = datetime.datetime.fromtimestamp(0)
2140 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002141 os.utime(filename, (timestamp, timestamp))
2142
2143 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2144 finally:
2145 os.chmod(filename, saved_stat.st_mode)
2146 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2147 zipfile.ZIP64_LIMIT = saved_zip64_limit
2148
2149
Tao Bao58c1b962015-05-20 09:32:18 -07002150def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002151 compress_type=None):
2152 """Wrap zipfile.writestr() function to work around the zip64 limit.
2153
2154 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2155 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2156 when calling crc32(bytes).
2157
2158 But it still works fine to write a shorter string into a large zip file.
2159 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2160 when we know the string won't be too long.
2161 """
2162
2163 saved_zip64_limit = zipfile.ZIP64_LIMIT
2164 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2165
2166 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2167 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002168 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002169 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002170 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002171 else:
Tao Baof3282b42015-04-01 11:21:55 -07002172 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002173 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2174 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2175 # such a case (since
2176 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2177 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2178 # permission bits. We follow the logic in Python 3 to get consistent
2179 # behavior between using the two versions.
2180 if not zinfo.external_attr:
2181 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002182
2183 # If compress_type is given, it overrides the value in zinfo.
2184 if compress_type is not None:
2185 zinfo.compress_type = compress_type
2186
Tao Bao58c1b962015-05-20 09:32:18 -07002187 # If perms is given, it has a priority.
2188 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002189 # If perms doesn't set the file type, mark it as a regular file.
2190 if perms & 0o770000 == 0:
2191 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002192 zinfo.external_attr = perms << 16
2193
Tao Baof3282b42015-04-01 11:21:55 -07002194 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002195 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2196
Dan Albert8b72aef2015-03-23 19:13:21 -07002197 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002198 zipfile.ZIP64_LIMIT = saved_zip64_limit
2199
2200
Tao Bao89d7ab22017-12-14 17:05:33 -08002201def ZipDelete(zip_filename, entries):
2202 """Deletes entries from a ZIP file.
2203
2204 Since deleting entries from a ZIP file is not supported, it shells out to
2205 'zip -d'.
2206
2207 Args:
2208 zip_filename: The name of the ZIP file.
2209 entries: The name of the entry, or the list of names to be deleted.
2210
2211 Raises:
2212 AssertionError: In case of non-zero return from 'zip'.
2213 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002214 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002215 entries = [entries]
2216 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002217 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002218
2219
Tao Baof3282b42015-04-01 11:21:55 -07002220def ZipClose(zip_file):
2221 # http://b/18015246
2222 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2223 # central directory.
2224 saved_zip64_limit = zipfile.ZIP64_LIMIT
2225 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2226
2227 zip_file.close()
2228
2229 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002230
2231
2232class DeviceSpecificParams(object):
2233 module = None
2234 def __init__(self, **kwargs):
2235 """Keyword arguments to the constructor become attributes of this
2236 object, which is passed to all functions in the device-specific
2237 module."""
Tao Bao38884282019-07-10 22:20:56 -07002238 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002239 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002240 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002241
2242 if self.module is None:
2243 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002244 if not path:
2245 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002246 try:
2247 if os.path.isdir(path):
2248 info = imp.find_module("releasetools", [path])
2249 else:
2250 d, f = os.path.split(path)
2251 b, x = os.path.splitext(f)
2252 if x == ".py":
2253 f = b
2254 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002255 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002256 self.module = imp.load_module("device_specific", *info)
2257 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002258 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002259
2260 def _DoCall(self, function_name, *args, **kwargs):
2261 """Call the named function in the device-specific module, passing
2262 the given args and kwargs. The first argument to the call will be
2263 the DeviceSpecific object itself. If there is no module, or the
2264 module does not define the function, return the value of the
2265 'default' kwarg (which itself defaults to None)."""
2266 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002267 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002268 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2269
2270 def FullOTA_Assertions(self):
2271 """Called after emitting the block of assertions at the top of a
2272 full OTA package. Implementations can add whatever additional
2273 assertions they like."""
2274 return self._DoCall("FullOTA_Assertions")
2275
Doug Zongkere5ff5902012-01-17 10:55:37 -08002276 def FullOTA_InstallBegin(self):
2277 """Called at the start of full OTA installation."""
2278 return self._DoCall("FullOTA_InstallBegin")
2279
Yifan Hong10c530d2018-12-27 17:34:18 -08002280 def FullOTA_GetBlockDifferences(self):
2281 """Called during full OTA installation and verification.
2282 Implementation should return a list of BlockDifference objects describing
2283 the update on each additional partitions.
2284 """
2285 return self._DoCall("FullOTA_GetBlockDifferences")
2286
Doug Zongker05d3dea2009-06-22 11:32:31 -07002287 def FullOTA_InstallEnd(self):
2288 """Called at the end of full OTA installation; typically this is
2289 used to install the image for the device's baseband processor."""
2290 return self._DoCall("FullOTA_InstallEnd")
2291
2292 def IncrementalOTA_Assertions(self):
2293 """Called after emitting the block of assertions at the top of an
2294 incremental OTA package. Implementations can add whatever
2295 additional assertions they like."""
2296 return self._DoCall("IncrementalOTA_Assertions")
2297
Doug Zongkere5ff5902012-01-17 10:55:37 -08002298 def IncrementalOTA_VerifyBegin(self):
2299 """Called at the start of the verification phase of incremental
2300 OTA installation; additional checks can be placed here to abort
2301 the script before any changes are made."""
2302 return self._DoCall("IncrementalOTA_VerifyBegin")
2303
Doug Zongker05d3dea2009-06-22 11:32:31 -07002304 def IncrementalOTA_VerifyEnd(self):
2305 """Called at the end of the verification phase of incremental OTA
2306 installation; additional checks can be placed here to abort the
2307 script before any changes are made."""
2308 return self._DoCall("IncrementalOTA_VerifyEnd")
2309
Doug Zongkere5ff5902012-01-17 10:55:37 -08002310 def IncrementalOTA_InstallBegin(self):
2311 """Called at the start of incremental OTA installation (after
2312 verification is complete)."""
2313 return self._DoCall("IncrementalOTA_InstallBegin")
2314
Yifan Hong10c530d2018-12-27 17:34:18 -08002315 def IncrementalOTA_GetBlockDifferences(self):
2316 """Called during incremental OTA installation and verification.
2317 Implementation should return a list of BlockDifference objects describing
2318 the update on each additional partitions.
2319 """
2320 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2321
Doug Zongker05d3dea2009-06-22 11:32:31 -07002322 def IncrementalOTA_InstallEnd(self):
2323 """Called at the end of incremental OTA installation; typically
2324 this is used to install the image for the device's baseband
2325 processor."""
2326 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002327
Tao Bao9bc6bb22015-11-09 16:58:28 -08002328 def VerifyOTA_Assertions(self):
2329 return self._DoCall("VerifyOTA_Assertions")
2330
Tao Bao76def242017-11-21 09:25:31 -08002331
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002332class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002333 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002334 self.name = name
2335 self.data = data
2336 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002337 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002338 self.sha1 = sha1(data).hexdigest()
2339
2340 @classmethod
2341 def FromLocalFile(cls, name, diskname):
2342 f = open(diskname, "rb")
2343 data = f.read()
2344 f.close()
2345 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002346
2347 def WriteToTemp(self):
2348 t = tempfile.NamedTemporaryFile()
2349 t.write(self.data)
2350 t.flush()
2351 return t
2352
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002353 def WriteToDir(self, d):
2354 with open(os.path.join(d, self.name), "wb") as fp:
2355 fp.write(self.data)
2356
Geremy Condra36bd3652014-02-06 19:45:10 -08002357 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002358 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002359
Tao Bao76def242017-11-21 09:25:31 -08002360
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002361DIFF_PROGRAM_BY_EXT = {
2362 ".gz" : "imgdiff",
2363 ".zip" : ["imgdiff", "-z"],
2364 ".jar" : ["imgdiff", "-z"],
2365 ".apk" : ["imgdiff", "-z"],
2366 ".img" : "imgdiff",
2367 }
2368
Tao Bao76def242017-11-21 09:25:31 -08002369
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002370class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002371 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002372 self.tf = tf
2373 self.sf = sf
2374 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002375 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002376
2377 def ComputePatch(self):
2378 """Compute the patch (as a string of data) needed to turn sf into
2379 tf. Returns the same tuple as GetPatch()."""
2380
2381 tf = self.tf
2382 sf = self.sf
2383
Doug Zongker24cd2802012-08-14 16:36:15 -07002384 if self.diff_program:
2385 diff_program = self.diff_program
2386 else:
2387 ext = os.path.splitext(tf.name)[1]
2388 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002389
2390 ttemp = tf.WriteToTemp()
2391 stemp = sf.WriteToTemp()
2392
2393 ext = os.path.splitext(tf.name)[1]
2394
2395 try:
2396 ptemp = tempfile.NamedTemporaryFile()
2397 if isinstance(diff_program, list):
2398 cmd = copy.copy(diff_program)
2399 else:
2400 cmd = [diff_program]
2401 cmd.append(stemp.name)
2402 cmd.append(ttemp.name)
2403 cmd.append(ptemp.name)
2404 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002405 err = []
2406 def run():
2407 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002408 if e:
2409 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002410 th = threading.Thread(target=run)
2411 th.start()
2412 th.join(timeout=300) # 5 mins
2413 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002414 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002415 p.terminate()
2416 th.join(5)
2417 if th.is_alive():
2418 p.kill()
2419 th.join()
2420
Tianjie Xua2a9f992018-01-05 15:15:54 -08002421 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002422 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002423 self.patch = None
2424 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002425 diff = ptemp.read()
2426 finally:
2427 ptemp.close()
2428 stemp.close()
2429 ttemp.close()
2430
2431 self.patch = diff
2432 return self.tf, self.sf, self.patch
2433
2434
2435 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002436 """Returns a tuple of (target_file, source_file, patch_data).
2437
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002438 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002439 computing the patch failed.
2440 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002441 return self.tf, self.sf, self.patch
2442
2443
2444def ComputeDifferences(diffs):
2445 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002446 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002447
2448 # Do the largest files first, to try and reduce the long-pole effect.
2449 by_size = [(i.tf.size, i) for i in diffs]
2450 by_size.sort(reverse=True)
2451 by_size = [i[1] for i in by_size]
2452
2453 lock = threading.Lock()
2454 diff_iter = iter(by_size) # accessed under lock
2455
2456 def worker():
2457 try:
2458 lock.acquire()
2459 for d in diff_iter:
2460 lock.release()
2461 start = time.time()
2462 d.ComputePatch()
2463 dur = time.time() - start
2464 lock.acquire()
2465
2466 tf, sf, patch = d.GetPatch()
2467 if sf.name == tf.name:
2468 name = tf.name
2469 else:
2470 name = "%s (%s)" % (tf.name, sf.name)
2471 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002472 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002473 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002474 logger.info(
2475 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2476 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002477 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002478 except Exception:
2479 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002480 raise
2481
2482 # start worker threads; wait for them all to finish.
2483 threads = [threading.Thread(target=worker)
2484 for i in range(OPTIONS.worker_threads)]
2485 for th in threads:
2486 th.start()
2487 while threads:
2488 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002489
2490
Dan Albert8b72aef2015-03-23 19:13:21 -07002491class BlockDifference(object):
2492 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002493 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002494 self.tgt = tgt
2495 self.src = src
2496 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002497 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002498 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002499
Tao Baodd2a5892015-03-12 12:32:37 -07002500 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002501 version = max(
2502 int(i) for i in
2503 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002504 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002505 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002506
Tianjie Xu41976c72019-07-03 13:57:01 -07002507 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2508 version=self.version,
2509 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002510 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002511 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002512 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002513 self.touched_src_ranges = b.touched_src_ranges
2514 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002515
Yifan Hong10c530d2018-12-27 17:34:18 -08002516 # On devices with dynamic partitions, for new partitions,
2517 # src is None but OPTIONS.source_info_dict is not.
2518 if OPTIONS.source_info_dict is None:
2519 is_dynamic_build = OPTIONS.info_dict.get(
2520 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002521 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002522 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002523 is_dynamic_build = OPTIONS.source_info_dict.get(
2524 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002525 is_dynamic_source = partition in shlex.split(
2526 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002527
Yifan Hongbb2658d2019-01-25 12:30:58 -08002528 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002529 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2530
Yifan Hongbb2658d2019-01-25 12:30:58 -08002531 # For dynamic partitions builds, check partition list in both source
2532 # and target build because new partitions may be added, and existing
2533 # partitions may be removed.
2534 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2535
Yifan Hong10c530d2018-12-27 17:34:18 -08002536 if is_dynamic:
2537 self.device = 'map_partition("%s")' % partition
2538 else:
2539 if OPTIONS.source_info_dict is None:
Yifan Hongae6e0d52020-05-07 12:38:53 -07002540 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2541 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002542 else:
Yifan Hongae6e0d52020-05-07 12:38:53 -07002543 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2544 OPTIONS.source_info_dict)
2545 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002546
Tao Baod8d14be2016-02-04 14:26:02 -08002547 @property
2548 def required_cache(self):
2549 return self._required_cache
2550
Tao Bao76def242017-11-21 09:25:31 -08002551 def WriteScript(self, script, output_zip, progress=None,
2552 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002553 if not self.src:
2554 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002555 script.Print("Patching %s image unconditionally..." % (self.partition,))
2556 else:
2557 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002558
Dan Albert8b72aef2015-03-23 19:13:21 -07002559 if progress:
2560 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002561 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002562
2563 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002564 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002565
Tao Bao9bc6bb22015-11-09 16:58:28 -08002566 def WriteStrictVerifyScript(self, script):
2567 """Verify all the blocks in the care_map, including clobbered blocks.
2568
2569 This differs from the WriteVerifyScript() function: a) it prints different
2570 error messages; b) it doesn't allow half-way updated images to pass the
2571 verification."""
2572
2573 partition = self.partition
2574 script.Print("Verifying %s..." % (partition,))
2575 ranges = self.tgt.care_map
2576 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002577 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002578 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2579 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002580 self.device, ranges_str,
2581 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002582 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002583 script.AppendExtra("")
2584
Tao Baod522bdc2016-04-12 15:53:16 -07002585 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002586 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002587
2588 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002589 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002590 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002591
2592 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002593 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002594 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002595 ranges = self.touched_src_ranges
2596 expected_sha1 = self.touched_src_sha1
2597 else:
2598 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2599 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002600
2601 # No blocks to be checked, skipping.
2602 if not ranges:
2603 return
2604
Tao Bao5ece99d2015-05-12 11:42:31 -07002605 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002606 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002607 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002608 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2609 '"%s.patch.dat")) then' % (
2610 self.device, ranges_str, expected_sha1,
2611 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002612 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002613 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002614
Tianjie Xufc3422a2015-12-15 11:53:59 -08002615 if self.version >= 4:
2616
2617 # Bug: 21124327
2618 # When generating incrementals for the system and vendor partitions in
2619 # version 4 or newer, explicitly check the first block (which contains
2620 # the superblock) of the partition to see if it's what we expect. If
2621 # this check fails, give an explicit log message about the partition
2622 # having been remounted R/W (the most likely explanation).
2623 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002624 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002625
2626 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002627 if partition == "system":
2628 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2629 else:
2630 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002631 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002632 'ifelse (block_image_recover({device}, "{ranges}") && '
2633 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002634 'package_extract_file("{partition}.transfer.list"), '
2635 '"{partition}.new.dat", "{partition}.patch.dat"), '
2636 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002637 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002638 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002639 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002640
Tao Baodd2a5892015-03-12 12:32:37 -07002641 # Abort the OTA update. Note that the incremental OTA cannot be applied
2642 # even if it may match the checksum of the target partition.
2643 # a) If version < 3, operations like move and erase will make changes
2644 # unconditionally and damage the partition.
2645 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002646 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002647 if partition == "system":
2648 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2649 else:
2650 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2651 script.AppendExtra((
2652 'abort("E%d: %s partition has unexpected contents");\n'
2653 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002654
Yifan Hong10c530d2018-12-27 17:34:18 -08002655 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002656 partition = self.partition
2657 script.Print('Verifying the updated %s image...' % (partition,))
2658 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2659 ranges = self.tgt.care_map
2660 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002661 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002662 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002663 self.device, ranges_str,
2664 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002665
2666 # Bug: 20881595
2667 # Verify that extended blocks are really zeroed out.
2668 if self.tgt.extended:
2669 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002670 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002671 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002672 self.device, ranges_str,
2673 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002674 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002675 if partition == "system":
2676 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2677 else:
2678 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002679 script.AppendExtra(
2680 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002681 ' abort("E%d: %s partition has unexpected non-zero contents after '
2682 'OTA update");\n'
2683 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002684 else:
2685 script.Print('Verified the updated %s image.' % (partition,))
2686
Tianjie Xu209db462016-05-24 17:34:52 -07002687 if partition == "system":
2688 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2689 else:
2690 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2691
Tao Bao5fcaaef2015-06-01 13:40:49 -07002692 script.AppendExtra(
2693 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002694 ' abort("E%d: %s partition has unexpected contents after OTA '
2695 'update");\n'
2696 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002697
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002698 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002699 ZipWrite(output_zip,
2700 '{}.transfer.list'.format(self.path),
2701 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002702
Tao Bao76def242017-11-21 09:25:31 -08002703 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2704 # its size. Quailty 9 almost triples the compression time but doesn't
2705 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002706 # zip | brotli(quality 6) | brotli(quality 9)
2707 # compressed_size: 942M | 869M (~8% reduced) | 854M
2708 # compression_time: 75s | 265s | 719s
2709 # decompression_time: 15s | 25s | 25s
2710
2711 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002712 brotli_cmd = ['brotli', '--quality=6',
2713 '--output={}.new.dat.br'.format(self.path),
2714 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002715 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002716 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002717
2718 new_data_name = '{}.new.dat.br'.format(self.partition)
2719 ZipWrite(output_zip,
2720 '{}.new.dat.br'.format(self.path),
2721 new_data_name,
2722 compress_type=zipfile.ZIP_STORED)
2723 else:
2724 new_data_name = '{}.new.dat'.format(self.partition)
2725 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2726
Dan Albert8e0178d2015-01-27 15:53:15 -08002727 ZipWrite(output_zip,
2728 '{}.patch.dat'.format(self.path),
2729 '{}.patch.dat'.format(self.partition),
2730 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002731
Tianjie Xu209db462016-05-24 17:34:52 -07002732 if self.partition == "system":
2733 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2734 else:
2735 code = ErrorCode.VENDOR_UPDATE_FAILURE
2736
Yifan Hong10c530d2018-12-27 17:34:18 -08002737 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002738 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002739 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002740 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002741 device=self.device, partition=self.partition,
2742 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002743 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002744
Dan Albert8b72aef2015-03-23 19:13:21 -07002745 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002746 data = source.ReadRangeSet(ranges)
2747 ctx = sha1()
2748
2749 for p in data:
2750 ctx.update(p)
2751
2752 return ctx.hexdigest()
2753
Tao Baoe9b61912015-07-09 17:37:49 -07002754 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2755 """Return the hash value for all zero blocks."""
2756 zero_block = '\x00' * 4096
2757 ctx = sha1()
2758 for _ in range(num_blocks):
2759 ctx.update(zero_block)
2760
2761 return ctx.hexdigest()
2762
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002763
Tianjie Xu41976c72019-07-03 13:57:01 -07002764# Expose these two classes to support vendor-specific scripts
2765DataImage = images.DataImage
2766EmptyImage = images.EmptyImage
2767
Tao Bao76def242017-11-21 09:25:31 -08002768
Doug Zongker96a57e72010-09-26 14:57:41 -07002769# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002770PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002771 "ext4": "EMMC",
2772 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002773 "f2fs": "EMMC",
2774 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002775}
Doug Zongker96a57e72010-09-26 14:57:41 -07002776
Yifan Hongae6e0d52020-05-07 12:38:53 -07002777def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2778 """
2779 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2780 backwards compatibility. It aborts if the fstab entry has slotselect option
2781 (unless check_no_slot is explicitly set to False).
2782 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002783 fstab = info["fstab"]
2784 if fstab:
Yifan Hongae6e0d52020-05-07 12:38:53 -07002785 if check_no_slot:
2786 assert not fstab[mount_point].slotselect, \
2787 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002788 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2789 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002790 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002791 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002792
2793
Yifan Hongae6e0d52020-05-07 12:38:53 -07002794def GetTypeAndDeviceExpr(mount_point, info):
2795 """
2796 Return the filesystem of the partition, and an edify expression that evaluates
2797 to the device at runtime.
2798 """
2799 fstab = info["fstab"]
2800 if fstab:
2801 p = fstab[mount_point]
2802 device_expr = '"%s"' % fstab[mount_point].device
2803 if p.slotselect:
2804 device_expr = 'add_slot_suffix(%s)' % device_expr
2805 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
2806 else:
2807 raise KeyError
2808
2809
2810def GetEntryForDevice(fstab, device):
2811 """
2812 Returns:
2813 The first entry in fstab whose device is the given value.
2814 """
2815 if not fstab:
2816 return None
2817 for mount_point in fstab:
2818 if fstab[mount_point].device == device:
2819 return fstab[mount_point]
2820 return None
2821
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002822def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002823 """Parses and converts a PEM-encoded certificate into DER-encoded.
2824
2825 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2826
2827 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002828 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002829 """
2830 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002831 save = False
2832 for line in data.split("\n"):
2833 if "--END CERTIFICATE--" in line:
2834 break
2835 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002836 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002837 if "--BEGIN CERTIFICATE--" in line:
2838 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002839 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002840 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002841
Tao Bao04e1f012018-02-04 12:13:35 -08002842
2843def ExtractPublicKey(cert):
2844 """Extracts the public key (PEM-encoded) from the given certificate file.
2845
2846 Args:
2847 cert: The certificate filename.
2848
2849 Returns:
2850 The public key string.
2851
2852 Raises:
2853 AssertionError: On non-zero return from 'openssl'.
2854 """
2855 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2856 # While openssl 1.1 writes the key into the given filename followed by '-out',
2857 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2858 # stdout instead.
2859 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2860 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2861 pubkey, stderrdata = proc.communicate()
2862 assert proc.returncode == 0, \
2863 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2864 return pubkey
2865
2866
Tao Bao1ac886e2019-06-26 11:58:22 -07002867def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002868 """Extracts the AVB public key from the given public or private key.
2869
2870 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002871 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002872 key: The input key file, which should be PEM-encoded public or private key.
2873
2874 Returns:
2875 The path to the extracted AVB public key file.
2876 """
2877 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2878 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002879 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002880 return output
2881
2882
Doug Zongker412c02f2014-02-13 10:58:24 -08002883def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2884 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002885 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002886
Tao Bao6d5d6232018-03-09 17:04:42 -08002887 Most of the space in the boot and recovery images is just the kernel, which is
2888 identical for the two, so the resulting patch should be efficient. Add it to
2889 the output zip, along with a shell script that is run from init.rc on first
2890 boot to actually do the patching and install the new recovery image.
2891
2892 Args:
2893 input_dir: The top-level input directory of the target-files.zip.
2894 output_sink: The callback function that writes the result.
2895 recovery_img: File object for the recovery image.
2896 boot_img: File objects for the boot image.
2897 info_dict: A dict returned by common.LoadInfoDict() on the input
2898 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002899 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002900 if info_dict is None:
2901 info_dict = OPTIONS.info_dict
2902
Tao Bao6d5d6232018-03-09 17:04:42 -08002903 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002904 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2905
2906 if board_uses_vendorimage:
2907 # In this case, the output sink is rooted at VENDOR
2908 recovery_img_path = "etc/recovery.img"
2909 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2910 sh_dir = "bin"
2911 else:
2912 # In this case the output sink is rooted at SYSTEM
2913 recovery_img_path = "vendor/etc/recovery.img"
2914 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2915 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002916
Tao Baof2cffbd2015-07-22 12:33:18 -07002917 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002918 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002919
2920 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002921 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002922 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002923 # With system-root-image, boot and recovery images will have mismatching
2924 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2925 # to handle such a case.
2926 if system_root_image:
2927 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002928 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002929 assert not os.path.exists(path)
2930 else:
2931 diff_program = ["imgdiff"]
2932 if os.path.exists(path):
2933 diff_program.append("-b")
2934 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002935 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002936 else:
2937 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002938
2939 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2940 _, _, patch = d.ComputePatch()
2941 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002942
Dan Albertebb19aa2015-03-27 19:11:53 -07002943 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002944 # The following GetTypeAndDevice()s need to use the path in the target
2945 # info_dict instead of source_info_dict.
Yifan Hongae6e0d52020-05-07 12:38:53 -07002946 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
2947 check_no_slot=False)
2948 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
2949 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07002950 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002951 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002952
Tao Baof2cffbd2015-07-22 12:33:18 -07002953 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002954
2955 # Note that we use /vendor to refer to the recovery resources. This will
2956 # work for a separate vendor partition mounted at /vendor or a
2957 # /system/vendor subdirectory on the system partition, for which init will
2958 # create a symlink from /vendor to /system/vendor.
2959
2960 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002961if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2962 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002963 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002964 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2965 log -t recovery "Installing new recovery image: succeeded" || \\
2966 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002967else
2968 log -t recovery "Recovery image already installed"
2969fi
2970""" % {'type': recovery_type,
2971 'device': recovery_device,
2972 'sha1': recovery_img.sha1,
2973 'size': recovery_img.size}
2974 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002975 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002976if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2977 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002978 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002979 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2980 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2981 log -t recovery "Installing new recovery image: succeeded" || \\
2982 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002983else
2984 log -t recovery "Recovery image already installed"
2985fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002986""" % {'boot_size': boot_img.size,
2987 'boot_sha1': boot_img.sha1,
2988 'recovery_size': recovery_img.size,
2989 'recovery_sha1': recovery_img.sha1,
2990 'boot_type': boot_type,
Yifan Hongae6e0d52020-05-07 12:38:53 -07002991 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee55f62c2020-05-19 13:44:26 -07002992 'recovery_type': recovery_type,
2993 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07002994 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002995
Bill Peckhame868aec2019-09-17 17:06:47 -07002996 # The install script location moved from /system/etc to /system/bin in the L
2997 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2998 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002999
Tao Bao32fcdab2018-10-12 10:30:39 -07003000 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003001
Tao Baoda30cfa2017-12-01 16:19:46 -08003002 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003003
3004
3005class DynamicPartitionUpdate(object):
3006 def __init__(self, src_group=None, tgt_group=None, progress=None,
3007 block_difference=None):
3008 self.src_group = src_group
3009 self.tgt_group = tgt_group
3010 self.progress = progress
3011 self.block_difference = block_difference
3012
3013 @property
3014 def src_size(self):
3015 if not self.block_difference:
3016 return 0
3017 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3018
3019 @property
3020 def tgt_size(self):
3021 if not self.block_difference:
3022 return 0
3023 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3024
3025 @staticmethod
3026 def _GetSparseImageSize(img):
3027 if not img:
3028 return 0
3029 return img.blocksize * img.total_blocks
3030
3031
3032class DynamicGroupUpdate(object):
3033 def __init__(self, src_size=None, tgt_size=None):
3034 # None: group does not exist. 0: no size limits.
3035 self.src_size = src_size
3036 self.tgt_size = tgt_size
3037
3038
3039class DynamicPartitionsDifference(object):
3040 def __init__(self, info_dict, block_diffs, progress_dict=None,
3041 source_info_dict=None):
3042 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003043 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003044
3045 self._remove_all_before_apply = False
3046 if source_info_dict is None:
3047 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003048 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003049
Tao Baof1113e92019-06-18 12:10:14 -07003050 block_diff_dict = collections.OrderedDict(
3051 [(e.partition, e) for e in block_diffs])
3052
Yifan Hong10c530d2018-12-27 17:34:18 -08003053 assert len(block_diff_dict) == len(block_diffs), \
3054 "Duplicated BlockDifference object for {}".format(
3055 [partition for partition, count in
3056 collections.Counter(e.partition for e in block_diffs).items()
3057 if count > 1])
3058
Yifan Hong79997e52019-01-23 16:56:19 -08003059 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003060
3061 for p, block_diff in block_diff_dict.items():
3062 self._partition_updates[p] = DynamicPartitionUpdate()
3063 self._partition_updates[p].block_difference = block_diff
3064
3065 for p, progress in progress_dict.items():
3066 if p in self._partition_updates:
3067 self._partition_updates[p].progress = progress
3068
3069 tgt_groups = shlex.split(info_dict.get(
3070 "super_partition_groups", "").strip())
3071 src_groups = shlex.split(source_info_dict.get(
3072 "super_partition_groups", "").strip())
3073
3074 for g in tgt_groups:
3075 for p in shlex.split(info_dict.get(
3076 "super_%s_partition_list" % g, "").strip()):
3077 assert p in self._partition_updates, \
3078 "{} is in target super_{}_partition_list but no BlockDifference " \
3079 "object is provided.".format(p, g)
3080 self._partition_updates[p].tgt_group = g
3081
3082 for g in src_groups:
3083 for p in shlex.split(source_info_dict.get(
3084 "super_%s_partition_list" % g, "").strip()):
3085 assert p in self._partition_updates, \
3086 "{} is in source super_{}_partition_list but no BlockDifference " \
3087 "object is provided.".format(p, g)
3088 self._partition_updates[p].src_group = g
3089
Yifan Hong45433e42019-01-18 13:55:25 -08003090 target_dynamic_partitions = set(shlex.split(info_dict.get(
3091 "dynamic_partition_list", "").strip()))
3092 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3093 if u.tgt_size)
3094 assert block_diffs_with_target == target_dynamic_partitions, \
3095 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3096 list(target_dynamic_partitions), list(block_diffs_with_target))
3097
3098 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3099 "dynamic_partition_list", "").strip()))
3100 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3101 if u.src_size)
3102 assert block_diffs_with_source == source_dynamic_partitions, \
3103 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3104 list(source_dynamic_partitions), list(block_diffs_with_source))
3105
Yifan Hong10c530d2018-12-27 17:34:18 -08003106 if self._partition_updates:
3107 logger.info("Updating dynamic partitions %s",
3108 self._partition_updates.keys())
3109
Yifan Hong79997e52019-01-23 16:56:19 -08003110 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003111
3112 for g in tgt_groups:
3113 self._group_updates[g] = DynamicGroupUpdate()
3114 self._group_updates[g].tgt_size = int(info_dict.get(
3115 "super_%s_group_size" % g, "0").strip())
3116
3117 for g in src_groups:
3118 if g not in self._group_updates:
3119 self._group_updates[g] = DynamicGroupUpdate()
3120 self._group_updates[g].src_size = int(source_info_dict.get(
3121 "super_%s_group_size" % g, "0").strip())
3122
3123 self._Compute()
3124
3125 def WriteScript(self, script, output_zip, write_verify_script=False):
3126 script.Comment('--- Start patching dynamic partitions ---')
3127 for p, u in self._partition_updates.items():
3128 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3129 script.Comment('Patch partition %s' % p)
3130 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3131 write_verify_script=False)
3132
3133 op_list_path = MakeTempFile()
3134 with open(op_list_path, 'w') as f:
3135 for line in self._op_list:
3136 f.write('{}\n'.format(line))
3137
3138 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3139
3140 script.Comment('Update dynamic partition metadata')
3141 script.AppendExtra('assert(update_dynamic_partitions('
3142 'package_extract_file("dynamic_partitions_op_list")));')
3143
3144 if write_verify_script:
3145 for p, u in self._partition_updates.items():
3146 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3147 u.block_difference.WritePostInstallVerifyScript(script)
3148 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3149
3150 for p, u in self._partition_updates.items():
3151 if u.tgt_size and u.src_size <= u.tgt_size:
3152 script.Comment('Patch partition %s' % p)
3153 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3154 write_verify_script=write_verify_script)
3155 if write_verify_script:
3156 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3157
3158 script.Comment('--- End patching dynamic partitions ---')
3159
3160 def _Compute(self):
3161 self._op_list = list()
3162
3163 def append(line):
3164 self._op_list.append(line)
3165
3166 def comment(line):
3167 self._op_list.append("# %s" % line)
3168
3169 if self._remove_all_before_apply:
3170 comment('Remove all existing dynamic partitions and groups before '
3171 'applying full OTA')
3172 append('remove_all_groups')
3173
3174 for p, u in self._partition_updates.items():
3175 if u.src_group and not u.tgt_group:
3176 append('remove %s' % p)
3177
3178 for p, u in self._partition_updates.items():
3179 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3180 comment('Move partition %s from %s to default' % (p, u.src_group))
3181 append('move %s default' % p)
3182
3183 for p, u in self._partition_updates.items():
3184 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3185 comment('Shrink partition %s from %d to %d' %
3186 (p, u.src_size, u.tgt_size))
3187 append('resize %s %s' % (p, u.tgt_size))
3188
3189 for g, u in self._group_updates.items():
3190 if u.src_size is not None and u.tgt_size is None:
3191 append('remove_group %s' % g)
3192 if (u.src_size is not None and u.tgt_size is not None and
3193 u.src_size > u.tgt_size):
3194 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3195 append('resize_group %s %d' % (g, u.tgt_size))
3196
3197 for g, u in self._group_updates.items():
3198 if u.src_size is None and u.tgt_size is not None:
3199 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3200 append('add_group %s %d' % (g, u.tgt_size))
3201 if (u.src_size is not None and u.tgt_size is not None and
3202 u.src_size < u.tgt_size):
3203 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3204 append('resize_group %s %d' % (g, u.tgt_size))
3205
3206 for p, u in self._partition_updates.items():
3207 if u.tgt_group and not u.src_group:
3208 comment('Add partition %s to group %s' % (p, u.tgt_group))
3209 append('add %s %s' % (p, u.tgt_group))
3210
3211 for p, u in self._partition_updates.items():
3212 if u.tgt_size and u.src_size < u.tgt_size:
3213 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3214 append('resize %s %d' % (p, u.tgt_size))
3215
3216 for p, u in self._partition_updates.items():
3217 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3218 comment('Move partition %s from default to %s' %
3219 (p, u.tgt_group))
3220 append('move %s %s' % (p, u.tgt_group))