blob: 10fdb6d6023e67bfc3b9ebe126b83871b80bf9ff [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
981 values. Only valid partition names are accepted, as listed in
982 common.AVB_PARTITIONS.
983 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
984 needed_partitions: Partitions whose descriptors should be included into the
985 generated VBMeta image.
986
987 Raises:
988 AssertionError: On invalid input args.
989 """
990 avbtool = OPTIONS.info_dict["avb_avbtool"]
991 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
992 AppendAVBSigningArgs(cmd, name)
993
994 for partition, path in partitions.items():
995 if partition not in needed_partitions:
996 continue
997 assert (partition in AVB_PARTITIONS or
998 partition in AVB_VBMETA_PARTITIONS), \
999 'Unknown partition: {}'.format(partition)
1000 assert os.path.exists(path), \
1001 'Failed to find {} for {}'.format(path, partition)
1002 cmd.extend(GetAvbPartitionArg(partition, path))
1003
1004 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1005 if args and args.strip():
1006 split_args = shlex.split(args)
1007 for index, arg in enumerate(split_args[:-1]):
1008 # Sanity check that the image file exists. Some images might be defined
1009 # as a path relative to source tree, which may not be available at the
1010 # same location when running this script (we have the input target_files
1011 # zip only). For such cases, we additionally scan other locations (e.g.
1012 # IMAGES/, RADIO/, etc) before bailing out.
1013 if arg == '--include_descriptors_from_image':
1014 image_path = split_args[index + 1]
1015 if os.path.exists(image_path):
1016 continue
1017 found = False
1018 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1019 alt_path = os.path.join(
1020 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
1021 if os.path.exists(alt_path):
1022 split_args[index + 1] = alt_path
1023 found = True
1024 break
1025 assert found, 'Failed to find {}'.format(image_path)
1026 cmd.extend(split_args)
1027
1028 RunAndCheckOutput(cmd)
1029
Dan Austin52903642019-12-12 15:44:00 -08001030 if OPTIONS.aftl_server is not None:
1031 # Ensure the other AFTL parameters are set as well.
1032 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1033 assert OPTIONS.aftl_manufacturer_key_path is not None, 'No AFTL manufacturer key provided.'
1034 assert OPTIONS.aftl_signer_helper is not None, 'No AFTL signer helper provided.'
1035 # AFTL inclusion proof generation code will go here.
Daniel Norman276f0622019-07-26 14:13:51 -07001036
Steve Mucklee1b10862019-07-10 10:49:37 -07001037def _MakeRamdisk(sourcedir, fs_config_file=None):
1038 ramdisk_img = tempfile.NamedTemporaryFile()
1039
1040 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1041 cmd = ["mkbootfs", "-f", fs_config_file,
1042 os.path.join(sourcedir, "RAMDISK")]
1043 else:
1044 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1045 p1 = Run(cmd, stdout=subprocess.PIPE)
1046 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1047
1048 p2.wait()
1049 p1.wait()
1050 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1051 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1052
1053 return ramdisk_img
1054
1055
Steve Mucklef83e3c32020-04-08 18:27:00 -07001056def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001057 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001058 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001059
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001060 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001061 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1062 we are building a two-step special image (i.e. building a recovery image to
1063 be loaded into /boot in two-step OTAs).
1064
1065 Return the image data, or None if sourcedir does not appear to contains files
1066 for building the requested image.
1067 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001068
Steve Mucklef83e3c32020-04-08 18:27:00 -07001069 # "boot" or "recovery", without extension.
1070 partition_name = os.path.basename(sourcedir).lower()
1071
1072 if partition_name == "recovery":
1073 kernel = "kernel"
1074 else:
1075 kernel = image_name.replace("boot", "kernel")
1076 kernel = kernel.replace(".img","")
1077 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001078 return None
1079
1080 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001081 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001082
Doug Zongkerd5131602012-08-02 14:46:42 -07001083 if info_dict is None:
1084 info_dict = OPTIONS.info_dict
1085
Doug Zongkereef39442009-04-02 12:14:19 -07001086 img = tempfile.NamedTemporaryFile()
1087
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001088 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001089 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001090
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001091 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1092 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1093
Steve Mucklef83e3c32020-04-08 18:27:00 -07001094 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001095
Benoit Fradina45a8682014-07-14 21:00:43 +02001096 fn = os.path.join(sourcedir, "second")
1097 if os.access(fn, os.F_OK):
1098 cmd.append("--second")
1099 cmd.append(fn)
1100
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001101 fn = os.path.join(sourcedir, "dtb")
1102 if os.access(fn, os.F_OK):
1103 cmd.append("--dtb")
1104 cmd.append(fn)
1105
Doug Zongker171f1cd2009-06-15 22:36:37 -07001106 fn = os.path.join(sourcedir, "cmdline")
1107 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001108 cmd.append("--cmdline")
1109 cmd.append(open(fn).read().rstrip("\n"))
1110
1111 fn = os.path.join(sourcedir, "base")
1112 if os.access(fn, os.F_OK):
1113 cmd.append("--base")
1114 cmd.append(open(fn).read().rstrip("\n"))
1115
Ying Wang4de6b5b2010-08-25 14:29:34 -07001116 fn = os.path.join(sourcedir, "pagesize")
1117 if os.access(fn, os.F_OK):
1118 cmd.append("--pagesize")
1119 cmd.append(open(fn).read().rstrip("\n"))
1120
Steve Muckle759d0c82020-03-16 19:13:46 -07001121 if partition_name == "recovery":
1122 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddye4d5d562020-05-04 19:40:16 +05301123 if not args:
1124 # Fall back to "mkbootimg_args" for recovery image
1125 # in case "recovery_mkbootimg_args" is not set.
1126 args = info_dict.get("mkbootimg_args")
Steve Muckle759d0c82020-03-16 19:13:46 -07001127 else:
1128 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001129 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001130 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001131
Tao Bao76def242017-11-21 09:25:31 -08001132 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001133 if args and args.strip():
1134 cmd.extend(shlex.split(args))
1135
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001136 if has_ramdisk:
1137 cmd.extend(["--ramdisk", ramdisk_img.name])
1138
Tao Baod95e9fd2015-03-29 23:07:41 -07001139 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001140 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001141 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001142 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001143 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001144 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001145
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001146 if partition_name == "recovery":
1147 if info_dict.get("include_recovery_dtbo") == "true":
1148 fn = os.path.join(sourcedir, "recovery_dtbo")
1149 cmd.extend(["--recovery_dtbo", fn])
1150 if info_dict.get("include_recovery_acpio") == "true":
1151 fn = os.path.join(sourcedir, "recovery_acpio")
1152 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001153
Tao Bao986ee862018-10-04 15:46:16 -07001154 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001155
Tao Bao76def242017-11-21 09:25:31 -08001156 if (info_dict.get("boot_signer") == "true" and
1157 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001158 # Hard-code the path as "/boot" for two-step special recovery image (which
1159 # will be loaded into /boot during the two-step OTA).
1160 if two_step_image:
1161 path = "/boot"
1162 else:
Tao Baobf70c312017-07-11 17:27:55 -07001163 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001164 cmd = [OPTIONS.boot_signer_path]
1165 cmd.extend(OPTIONS.boot_signer_args)
1166 cmd.extend([path, img.name,
1167 info_dict["verity_key"] + ".pk8",
1168 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001169 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001170
Tao Baod95e9fd2015-03-29 23:07:41 -07001171 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001172 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001173 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001174 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001175 # We have switched from the prebuilt futility binary to using the tool
1176 # (futility-host) built from the source. Override the setting in the old
1177 # TF.zip.
1178 futility = info_dict["futility"]
1179 if futility.startswith("prebuilts/"):
1180 futility = "futility-host"
1181 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001182 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001183 info_dict["vboot_key"] + ".vbprivk",
1184 info_dict["vboot_subkey"] + ".vbprivk",
1185 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001186 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001187 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001188
Tao Baof3282b42015-04-01 11:21:55 -07001189 # Clean up the temp files.
1190 img_unsigned.close()
1191 img_keyblock.close()
1192
David Zeuthen8fecb282017-12-01 16:24:01 -05001193 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001194 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001195 avbtool = info_dict["avb_avbtool"]
Steve Muckle53226682020-05-07 17:32:10 -07001196 if partition_name == "recovery":
1197 part_size = info_dict["recovery_size"]
1198 else:
1199 part_size = info_dict[image_name.replace(".img","_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001200 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001201 "--partition_size", str(part_size), "--partition_name",
1202 partition_name]
1203 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001204 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001205 if args and args.strip():
1206 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001207 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001208
1209 img.seek(os.SEEK_SET, 0)
1210 data = img.read()
1211
1212 if has_ramdisk:
1213 ramdisk_img.close()
1214 img.close()
1215
1216 return data
1217
1218
Doug Zongkerd5131602012-08-02 14:46:42 -07001219def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001220 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001221 """Return a File object with the desired bootable image.
1222
1223 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1224 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1225 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001226
Doug Zongker55d93282011-01-25 17:03:34 -08001227 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1228 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001229 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001230 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001231
1232 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1233 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001234 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001235 return File.FromLocalFile(name, prebuilt_path)
1236
Tao Bao32fcdab2018-10-12 10:30:39 -07001237 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001238
1239 if info_dict is None:
1240 info_dict = OPTIONS.info_dict
1241
1242 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001243 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1244 # for recovery.
1245 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1246 prebuilt_name != "boot.img" or
1247 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001248
Doug Zongker6f1d0312014-08-22 08:07:12 -07001249 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Mucklef83e3c32020-04-08 18:27:00 -07001250 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001251 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001252 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001253 if data:
1254 return File(name, data)
1255 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001256
Doug Zongkereef39442009-04-02 12:14:19 -07001257
Steve Mucklee1b10862019-07-10 10:49:37 -07001258def _BuildVendorBootImage(sourcedir, info_dict=None):
1259 """Build a vendor boot image from the specified sourcedir.
1260
1261 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1262 turn them into a vendor boot image.
1263
1264 Return the image data, or None if sourcedir does not appear to contains files
1265 for building the requested image.
1266 """
1267
1268 if info_dict is None:
1269 info_dict = OPTIONS.info_dict
1270
1271 img = tempfile.NamedTemporaryFile()
1272
1273 ramdisk_img = _MakeRamdisk(sourcedir)
1274
1275 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1276 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1277
1278 cmd = [mkbootimg]
1279
1280 fn = os.path.join(sourcedir, "dtb")
1281 if os.access(fn, os.F_OK):
1282 cmd.append("--dtb")
1283 cmd.append(fn)
1284
1285 fn = os.path.join(sourcedir, "vendor_cmdline")
1286 if os.access(fn, os.F_OK):
1287 cmd.append("--vendor_cmdline")
1288 cmd.append(open(fn).read().rstrip("\n"))
1289
1290 fn = os.path.join(sourcedir, "base")
1291 if os.access(fn, os.F_OK):
1292 cmd.append("--base")
1293 cmd.append(open(fn).read().rstrip("\n"))
1294
1295 fn = os.path.join(sourcedir, "pagesize")
1296 if os.access(fn, os.F_OK):
1297 cmd.append("--pagesize")
1298 cmd.append(open(fn).read().rstrip("\n"))
1299
1300 args = info_dict.get("mkbootimg_args")
1301 if args and args.strip():
1302 cmd.extend(shlex.split(args))
1303
1304 args = info_dict.get("mkbootimg_version_args")
1305 if args and args.strip():
1306 cmd.extend(shlex.split(args))
1307
1308 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1309 cmd.extend(["--vendor_boot", img.name])
1310
1311 RunAndCheckOutput(cmd)
1312
1313 # AVB: if enabled, calculate and add hash.
1314 if info_dict.get("avb_enable") == "true":
1315 avbtool = info_dict["avb_avbtool"]
1316 part_size = info_dict["vendor_boot_size"]
1317 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001318 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001319 AppendAVBSigningArgs(cmd, "vendor_boot")
1320 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1321 if args and args.strip():
1322 cmd.extend(shlex.split(args))
1323 RunAndCheckOutput(cmd)
1324
1325 img.seek(os.SEEK_SET, 0)
1326 data = img.read()
1327
1328 ramdisk_img.close()
1329 img.close()
1330
1331 return data
1332
1333
1334def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1335 info_dict=None):
1336 """Return a File object with the desired vendor boot image.
1337
1338 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1339 the source files in 'unpack_dir'/'tree_subdir'."""
1340
1341 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1342 if os.path.exists(prebuilt_path):
1343 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1344 return File.FromLocalFile(name, prebuilt_path)
1345
1346 logger.info("building image from target_files %s...", tree_subdir)
1347
1348 if info_dict is None:
1349 info_dict = OPTIONS.info_dict
1350
1351 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1352 if data:
1353 return File(name, data)
1354 return None
1355
1356
Narayan Kamatha07bf042017-08-14 14:49:21 +01001357def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001358 """Gunzips the given gzip compressed file to a given output file."""
1359 with gzip.open(in_filename, "rb") as in_file, \
1360 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001361 shutil.copyfileobj(in_file, out_file)
1362
1363
Tao Bao0ff15de2019-03-20 11:26:06 -07001364def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001365 """Unzips the archive to the given directory.
1366
1367 Args:
1368 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001369 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001370 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1371 archvie. Non-matching patterns will be filtered out. If there's no match
1372 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001373 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001374 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001375 if patterns is not None:
1376 # Filter out non-matching patterns. unzip will complain otherwise.
1377 with zipfile.ZipFile(filename) as input_zip:
1378 names = input_zip.namelist()
1379 filtered = [
1380 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1381
1382 # There isn't any matching files. Don't unzip anything.
1383 if not filtered:
1384 return
1385 cmd.extend(filtered)
1386
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001387 RunAndCheckOutput(cmd)
1388
1389
Doug Zongker75f17362009-12-08 13:46:44 -08001390def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001391 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001392
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001393 Args:
1394 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1395 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1396
1397 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1398 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001399
Tao Bao1c830bf2017-12-25 10:43:47 -08001400 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001401 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001402 """
Doug Zongkereef39442009-04-02 12:14:19 -07001403
Tao Bao1c830bf2017-12-25 10:43:47 -08001404 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001405 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1406 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001407 UnzipToDir(m.group(1), tmp, pattern)
1408 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001409 filename = m.group(1)
1410 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001411 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001412
Tao Baodba59ee2018-01-09 13:21:02 -08001413 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001414
1415
Yifan Hong8a66a712019-04-04 15:37:57 -07001416def GetUserImage(which, tmpdir, input_zip,
1417 info_dict=None,
1418 allow_shared_blocks=None,
1419 hashtree_info_generator=None,
1420 reset_file_map=False):
1421 """Returns an Image object suitable for passing to BlockImageDiff.
1422
1423 This function loads the specified image from the given path. If the specified
1424 image is sparse, it also performs additional processing for OTA purpose. For
1425 example, it always adds block 0 to clobbered blocks list. It also detects
1426 files that cannot be reconstructed from the block list, for whom we should
1427 avoid applying imgdiff.
1428
1429 Args:
1430 which: The partition name.
1431 tmpdir: The directory that contains the prebuilt image and block map file.
1432 input_zip: The target-files ZIP archive.
1433 info_dict: The dict to be looked up for relevant info.
1434 allow_shared_blocks: If image is sparse, whether having shared blocks is
1435 allowed. If none, it is looked up from info_dict.
1436 hashtree_info_generator: If present and image is sparse, generates the
1437 hashtree_info for this sparse image.
1438 reset_file_map: If true and image is sparse, reset file map before returning
1439 the image.
1440 Returns:
1441 A Image object. If it is a sparse image and reset_file_map is False, the
1442 image will have file_map info loaded.
1443 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001444 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001445 info_dict = LoadInfoDict(input_zip)
1446
1447 is_sparse = info_dict.get("extfs_sparse_flag")
1448
1449 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1450 # shared blocks (i.e. some blocks will show up in multiple files' block
1451 # list). We can only allocate such shared blocks to the first "owner", and
1452 # disable imgdiff for all later occurrences.
1453 if allow_shared_blocks is None:
1454 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1455
1456 if is_sparse:
1457 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1458 hashtree_info_generator)
1459 if reset_file_map:
1460 img.ResetFileMap()
1461 return img
1462 else:
1463 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1464
1465
1466def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1467 """Returns a Image object suitable for passing to BlockImageDiff.
1468
1469 This function loads the specified non-sparse image from the given path.
1470
1471 Args:
1472 which: The partition name.
1473 tmpdir: The directory that contains the prebuilt image and block map file.
1474 Returns:
1475 A Image object.
1476 """
1477 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1478 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1479
1480 # The image and map files must have been created prior to calling
1481 # ota_from_target_files.py (since LMP).
1482 assert os.path.exists(path) and os.path.exists(mappath)
1483
Tianjie Xu41976c72019-07-03 13:57:01 -07001484 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1485
Yifan Hong8a66a712019-04-04 15:37:57 -07001486
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001487def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1488 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001489 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1490
1491 This function loads the specified sparse image from the given path, and
1492 performs additional processing for OTA purpose. For example, it always adds
1493 block 0 to clobbered blocks list. It also detects files that cannot be
1494 reconstructed from the block list, for whom we should avoid applying imgdiff.
1495
1496 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001497 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001498 tmpdir: The directory that contains the prebuilt image and block map file.
1499 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001500 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001501 hashtree_info_generator: If present, generates the hashtree_info for this
1502 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001503 Returns:
1504 A SparseImage object, with file_map info loaded.
1505 """
Tao Baoc765cca2018-01-31 17:32:40 -08001506 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1507 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1508
1509 # The image and map files must have been created prior to calling
1510 # ota_from_target_files.py (since LMP).
1511 assert os.path.exists(path) and os.path.exists(mappath)
1512
1513 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1514 # it to clobbered_blocks so that it will be written to the target
1515 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1516 clobbered_blocks = "0"
1517
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001518 image = sparse_img.SparseImage(
1519 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1520 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001521
1522 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1523 # if they contain all zeros. We can't reconstruct such a file from its block
1524 # list. Tag such entries accordingly. (Bug: 65213616)
1525 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001526 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001527 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001528 continue
1529
Tom Cherryd14b8952018-08-09 14:26:00 -07001530 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1531 # filename listed in system.map may contain an additional leading slash
1532 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1533 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001534 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001535
Tom Cherryd14b8952018-08-09 14:26:00 -07001536 # Special handling another case, where files not under /system
1537 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001538 if which == 'system' and not arcname.startswith('SYSTEM'):
1539 arcname = 'ROOT/' + arcname
1540
1541 assert arcname in input_zip.namelist(), \
1542 "Failed to find the ZIP entry for {}".format(entry)
1543
Tao Baoc765cca2018-01-31 17:32:40 -08001544 info = input_zip.getinfo(arcname)
1545 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001546
1547 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001548 # image, check the original block list to determine its completeness. Note
1549 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001550 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001551 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001552
Tao Baoc765cca2018-01-31 17:32:40 -08001553 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1554 ranges.extra['incomplete'] = True
1555
1556 return image
1557
1558
Doug Zongkereef39442009-04-02 12:14:19 -07001559def GetKeyPasswords(keylist):
1560 """Given a list of keys, prompt the user to enter passwords for
1561 those which require them. Return a {key: password} dict. password
1562 will be None if the key has no password."""
1563
Doug Zongker8ce7c252009-05-22 13:34:54 -07001564 no_passwords = []
1565 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001566 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001567 devnull = open("/dev/null", "w+b")
1568 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001569 # We don't need a password for things that aren't really keys.
1570 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001571 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001572 continue
1573
T.R. Fullhart37e10522013-03-18 10:31:26 -07001574 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001575 "-inform", "DER", "-nocrypt"],
1576 stdin=devnull.fileno(),
1577 stdout=devnull.fileno(),
1578 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001579 p.communicate()
1580 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001581 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001582 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001583 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001584 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1585 "-inform", "DER", "-passin", "pass:"],
1586 stdin=devnull.fileno(),
1587 stdout=devnull.fileno(),
1588 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001589 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001590 if p.returncode == 0:
1591 # Encrypted key with empty string as password.
1592 key_passwords[k] = ''
1593 elif stderr.startswith('Error decrypting key'):
1594 # Definitely encrypted key.
1595 # It would have said "Error reading key" if it didn't parse correctly.
1596 need_passwords.append(k)
1597 else:
1598 # Potentially, a type of key that openssl doesn't understand.
1599 # We'll let the routines in signapk.jar handle it.
1600 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001601 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001602
T.R. Fullhart37e10522013-03-18 10:31:26 -07001603 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001604 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001605 return key_passwords
1606
1607
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001608def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001609 """Gets the minSdkVersion declared in the APK.
1610
changho.shin0f125362019-07-08 10:59:00 +09001611 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001612 This can be both a decimal number (API Level) or a codename.
1613
1614 Args:
1615 apk_name: The APK filename.
1616
1617 Returns:
1618 The parsed SDK version string.
1619
1620 Raises:
1621 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001622 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001623 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001624 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001625 stderr=subprocess.PIPE)
1626 stdoutdata, stderrdata = proc.communicate()
1627 if proc.returncode != 0:
1628 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001629 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001630 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001631
Tao Baof47bf0f2018-03-21 23:28:51 -07001632 for line in stdoutdata.split("\n"):
1633 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001634 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1635 if m:
1636 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001637 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001638
1639
1640def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001641 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001642
Tao Baof47bf0f2018-03-21 23:28:51 -07001643 If minSdkVersion is set to a codename, it is translated to a number using the
1644 provided map.
1645
1646 Args:
1647 apk_name: The APK filename.
1648
1649 Returns:
1650 The parsed SDK version number.
1651
1652 Raises:
1653 ExternalError: On failing to get the min SDK version number.
1654 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001655 version = GetMinSdkVersion(apk_name)
1656 try:
1657 return int(version)
1658 except ValueError:
1659 # Not a decimal number. Codename?
1660 if version in codename_to_api_level_map:
1661 return codename_to_api_level_map[version]
1662 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001663 raise ExternalError(
1664 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1665 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001666
1667
1668def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001669 codename_to_api_level_map=None, whole_file=False,
1670 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001671 """Sign the input_name zip/jar/apk, producing output_name. Use the
1672 given key and password (the latter may be None if the key does not
1673 have a password.
1674
Doug Zongker951495f2009-08-14 12:44:19 -07001675 If whole_file is true, use the "-w" option to SignApk to embed a
1676 signature that covers the whole file in the archive comment of the
1677 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001678
1679 min_api_level is the API Level (int) of the oldest platform this file may end
1680 up on. If not specified for an APK, the API Level is obtained by interpreting
1681 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1682
1683 codename_to_api_level_map is needed to translate the codename which may be
1684 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001685
1686 Caller may optionally specify extra args to be passed to SignApk, which
1687 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001688 """
Tao Bao76def242017-11-21 09:25:31 -08001689 if codename_to_api_level_map is None:
1690 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001691 if extra_signapk_args is None:
1692 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001693
Alex Klyubin9667b182015-12-10 13:38:50 -08001694 java_library_path = os.path.join(
1695 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1696
Tao Baoe95540e2016-11-08 12:08:53 -08001697 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1698 ["-Djava.library.path=" + java_library_path,
1699 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001700 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001701 if whole_file:
1702 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001703
1704 min_sdk_version = min_api_level
1705 if min_sdk_version is None:
1706 if not whole_file:
1707 min_sdk_version = GetMinSdkVersionInt(
1708 input_name, codename_to_api_level_map)
1709 if min_sdk_version is not None:
1710 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1711
T.R. Fullhart37e10522013-03-18 10:31:26 -07001712 cmd.extend([key + OPTIONS.public_key_suffix,
1713 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001714 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001715
Tao Bao73dd4f42018-10-04 16:25:33 -07001716 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001717 if password is not None:
1718 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001719 stdoutdata, _ = proc.communicate(password)
1720 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001721 raise ExternalError(
1722 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001723 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001724
Doug Zongkereef39442009-04-02 12:14:19 -07001725
Doug Zongker37974732010-09-16 17:44:38 -07001726def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001727 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001728
Tao Bao9dd909e2017-11-14 11:27:32 -08001729 For non-AVB images, raise exception if the data is too big. Print a warning
1730 if the data is nearing the maximum size.
1731
1732 For AVB images, the actual image size should be identical to the limit.
1733
1734 Args:
1735 data: A string that contains all the data for the partition.
1736 target: The partition name. The ".img" suffix is optional.
1737 info_dict: The dict to be looked up for relevant info.
1738 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001739 if target.endswith(".img"):
1740 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001741 mount_point = "/" + target
1742
Ying Wangf8824af2014-06-03 14:07:27 -07001743 fs_type = None
1744 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001745 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001746 if mount_point == "/userdata":
1747 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001748 p = info_dict["fstab"][mount_point]
1749 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001750 device = p.device
1751 if "/" in device:
1752 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001753 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001754 if not fs_type or not limit:
1755 return
Doug Zongkereef39442009-04-02 12:14:19 -07001756
Andrew Boie0f9aec82012-02-14 09:32:52 -08001757 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001758 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1759 # path.
1760 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1761 if size != limit:
1762 raise ExternalError(
1763 "Mismatching image size for %s: expected %d actual %d" % (
1764 target, limit, size))
1765 else:
1766 pct = float(size) * 100.0 / limit
1767 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1768 if pct >= 99.0:
1769 raise ExternalError(msg)
1770 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001771 logger.warning("\n WARNING: %s\n", msg)
1772 else:
1773 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001774
1775
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001776def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001777 """Parses the APK certs info from a given target-files zip.
1778
1779 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1780 tuple with the following elements: (1) a dictionary that maps packages to
1781 certs (based on the "certificate" and "private_key" attributes in the file;
1782 (2) a string representing the extension of compressed APKs in the target files
1783 (e.g ".gz", ".bro").
1784
1785 Args:
1786 tf_zip: The input target_files ZipFile (already open).
1787
1788 Returns:
1789 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1790 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1791 no compressed APKs.
1792 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001793 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001794 compressed_extension = None
1795
Tao Bao0f990332017-09-08 19:02:54 -07001796 # META/apkcerts.txt contains the info for _all_ the packages known at build
1797 # time. Filter out the ones that are not installed.
1798 installed_files = set()
1799 for name in tf_zip.namelist():
1800 basename = os.path.basename(name)
1801 if basename:
1802 installed_files.add(basename)
1803
Tao Baoda30cfa2017-12-01 16:19:46 -08001804 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001805 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001806 if not line:
1807 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001808 m = re.match(
1809 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham96c9e6e2020-04-03 15:36:23 -07001810 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1811 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001812 line)
1813 if not m:
1814 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001815
Tao Bao818ddf52018-01-05 11:17:34 -08001816 matches = m.groupdict()
1817 cert = matches["CERT"]
1818 privkey = matches["PRIVKEY"]
1819 name = matches["NAME"]
1820 this_compressed_extension = matches["COMPRESSED"]
1821
1822 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1823 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1824 if cert in SPECIAL_CERT_STRINGS and not privkey:
1825 certmap[name] = cert
1826 elif (cert.endswith(OPTIONS.public_key_suffix) and
1827 privkey.endswith(OPTIONS.private_key_suffix) and
1828 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1829 certmap[name] = cert[:-public_key_suffix_len]
1830 else:
1831 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1832
1833 if not this_compressed_extension:
1834 continue
1835
1836 # Only count the installed files.
1837 filename = name + '.' + this_compressed_extension
1838 if filename not in installed_files:
1839 continue
1840
1841 # Make sure that all the values in the compression map have the same
1842 # extension. We don't support multiple compression methods in the same
1843 # system image.
1844 if compressed_extension:
1845 if this_compressed_extension != compressed_extension:
1846 raise ValueError(
1847 "Multiple compressed extensions: {} vs {}".format(
1848 compressed_extension, this_compressed_extension))
1849 else:
1850 compressed_extension = this_compressed_extension
1851
1852 return (certmap,
1853 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001854
1855
Doug Zongkereef39442009-04-02 12:14:19 -07001856COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001857Global options
1858
1859 -p (--path) <dir>
1860 Prepend <dir>/bin to the list of places to search for binaries run by this
1861 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001862
Doug Zongker05d3dea2009-06-22 11:32:31 -07001863 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001864 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001865
Tao Bao30df8b42018-04-23 15:32:53 -07001866 -x (--extra) <key=value>
1867 Add a key/value pair to the 'extras' dict, which device-specific extension
1868 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001869
Doug Zongkereef39442009-04-02 12:14:19 -07001870 -v (--verbose)
1871 Show command lines being executed.
1872
1873 -h (--help)
1874 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001875
1876 --logfile <file>
1877 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001878"""
1879
1880def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001881 print(docstring.rstrip("\n"))
1882 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001883
1884
1885def ParseOptions(argv,
1886 docstring,
1887 extra_opts="", extra_long_opts=(),
1888 extra_option_handler=None):
1889 """Parse the options in argv and return any arguments that aren't
1890 flags. docstring is the calling module's docstring, to be displayed
1891 for errors and -h. extra_opts and extra_long_opts are for flags
1892 defined by the caller, which are processed by passing them to
1893 extra_option_handler."""
1894
1895 try:
1896 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001897 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001898 ["help", "verbose", "path=", "signapk_path=",
1899 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08001900 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001901 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1902 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Dan Austin52903642019-12-12 15:44:00 -08001903 "extra=", "logfile=", "aftl_server=", "aftl_key_path=",
1904 "aftl_manufacturer_key_path=", "aftl_signer_helper="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001905 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001906 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001907 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001908 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001909 sys.exit(2)
1910
Doug Zongkereef39442009-04-02 12:14:19 -07001911 for o, a in opts:
1912 if o in ("-h", "--help"):
1913 Usage(docstring)
1914 sys.exit()
1915 elif o in ("-v", "--verbose"):
1916 OPTIONS.verbose = True
1917 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001918 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001919 elif o in ("--signapk_path",):
1920 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001921 elif o in ("--signapk_shared_library_path",):
1922 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001923 elif o in ("--extra_signapk_args",):
1924 OPTIONS.extra_signapk_args = shlex.split(a)
1925 elif o in ("--java_path",):
1926 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001927 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001928 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08001929 elif o in ("--android_jar_path",):
1930 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001931 elif o in ("--public_key_suffix",):
1932 OPTIONS.public_key_suffix = a
1933 elif o in ("--private_key_suffix",):
1934 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001935 elif o in ("--boot_signer_path",):
1936 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001937 elif o in ("--boot_signer_args",):
1938 OPTIONS.boot_signer_args = shlex.split(a)
1939 elif o in ("--verity_signer_path",):
1940 OPTIONS.verity_signer_path = a
1941 elif o in ("--verity_signer_args",):
1942 OPTIONS.verity_signer_args = shlex.split(a)
Dan Austin52903642019-12-12 15:44:00 -08001943 elif o in ("--aftl_server",):
1944 OPTIONS.aftl_server = a
1945 elif o in ("--aftl_key_path",):
1946 OPTIONS.aftl_key_path = a
1947 elif o in ("--aftl_manufacturer_key_path",):
1948 OPTIONS.aftl_manufacturer_key_path = a
1949 elif o in ("--aftl_signer_helper",):
1950 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07001951 elif o in ("-s", "--device_specific"):
1952 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001953 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001954 key, value = a.split("=", 1)
1955 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001956 elif o in ("--logfile",):
1957 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001958 else:
1959 if extra_option_handler is None or not extra_option_handler(o, a):
1960 assert False, "unknown option \"%s\"" % (o,)
1961
Doug Zongker85448772014-09-09 14:59:20 -07001962 if OPTIONS.search_path:
1963 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1964 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001965
1966 return args
1967
1968
Tao Bao4c851b12016-09-19 13:54:38 -07001969def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001970 """Make a temp file and add it to the list of things to be deleted
1971 when Cleanup() is called. Return the filename."""
1972 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1973 os.close(fd)
1974 OPTIONS.tempfiles.append(fn)
1975 return fn
1976
1977
Tao Bao1c830bf2017-12-25 10:43:47 -08001978def MakeTempDir(prefix='tmp', suffix=''):
1979 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1980
1981 Returns:
1982 The absolute pathname of the new directory.
1983 """
1984 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1985 OPTIONS.tempfiles.append(dir_name)
1986 return dir_name
1987
1988
Doug Zongkereef39442009-04-02 12:14:19 -07001989def Cleanup():
1990 for i in OPTIONS.tempfiles:
1991 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001992 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001993 else:
1994 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001995 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001996
1997
1998class PasswordManager(object):
1999 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002000 self.editor = os.getenv("EDITOR")
2001 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002002
2003 def GetPasswords(self, items):
2004 """Get passwords corresponding to each string in 'items',
2005 returning a dict. (The dict may have keys in addition to the
2006 values in 'items'.)
2007
2008 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2009 user edit that file to add more needed passwords. If no editor is
2010 available, or $ANDROID_PW_FILE isn't define, prompts the user
2011 interactively in the ordinary way.
2012 """
2013
2014 current = self.ReadFile()
2015
2016 first = True
2017 while True:
2018 missing = []
2019 for i in items:
2020 if i not in current or not current[i]:
2021 missing.append(i)
2022 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002023 if not missing:
2024 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002025
2026 for i in missing:
2027 current[i] = ""
2028
2029 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002030 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002031 if sys.version_info[0] >= 3:
2032 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002033 answer = raw_input("try to edit again? [y]> ").strip()
2034 if answer and answer[0] not in 'yY':
2035 raise RuntimeError("key passwords unavailable")
2036 first = False
2037
2038 current = self.UpdateAndReadFile(current)
2039
Dan Albert8b72aef2015-03-23 19:13:21 -07002040 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002041 """Prompt the user to enter a value (password) for each key in
2042 'current' whose value is fales. Returns a new dict with all the
2043 values.
2044 """
2045 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002046 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002047 if v:
2048 result[k] = v
2049 else:
2050 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002051 result[k] = getpass.getpass(
2052 "Enter password for %s key> " % k).strip()
2053 if result[k]:
2054 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002055 return result
2056
2057 def UpdateAndReadFile(self, current):
2058 if not self.editor or not self.pwfile:
2059 return self.PromptResult(current)
2060
2061 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002062 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002063 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2064 f.write("# (Additional spaces are harmless.)\n\n")
2065
2066 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002067 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002068 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002069 f.write("[[[ %s ]]] %s\n" % (v, k))
2070 if not v and first_line is None:
2071 # position cursor on first line with no password.
2072 first_line = i + 4
2073 f.close()
2074
Tao Bao986ee862018-10-04 15:46:16 -07002075 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002076
2077 return self.ReadFile()
2078
2079 def ReadFile(self):
2080 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002081 if self.pwfile is None:
2082 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002083 try:
2084 f = open(self.pwfile, "r")
2085 for line in f:
2086 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002087 if not line or line[0] == '#':
2088 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002089 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2090 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002091 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002092 else:
2093 result[m.group(2)] = m.group(1)
2094 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002095 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002096 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002097 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002098 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002099
2100
Dan Albert8e0178d2015-01-27 15:53:15 -08002101def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2102 compress_type=None):
2103 import datetime
2104
2105 # http://b/18015246
2106 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2107 # for files larger than 2GiB. We can work around this by adjusting their
2108 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2109 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2110 # it isn't clear to me exactly what circumstances cause this).
2111 # `zipfile.write()` must be used directly to work around this.
2112 #
2113 # This mess can be avoided if we port to python3.
2114 saved_zip64_limit = zipfile.ZIP64_LIMIT
2115 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2116
2117 if compress_type is None:
2118 compress_type = zip_file.compression
2119 if arcname is None:
2120 arcname = filename
2121
2122 saved_stat = os.stat(filename)
2123
2124 try:
2125 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2126 # file to be zipped and reset it when we're done.
2127 os.chmod(filename, perms)
2128
2129 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002130 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2131 # intentional. zip stores datetimes in local time without a time zone
2132 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2133 # in the zip archive.
2134 local_epoch = datetime.datetime.fromtimestamp(0)
2135 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002136 os.utime(filename, (timestamp, timestamp))
2137
2138 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2139 finally:
2140 os.chmod(filename, saved_stat.st_mode)
2141 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2142 zipfile.ZIP64_LIMIT = saved_zip64_limit
2143
2144
Tao Bao58c1b962015-05-20 09:32:18 -07002145def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002146 compress_type=None):
2147 """Wrap zipfile.writestr() function to work around the zip64 limit.
2148
2149 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2150 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2151 when calling crc32(bytes).
2152
2153 But it still works fine to write a shorter string into a large zip file.
2154 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2155 when we know the string won't be too long.
2156 """
2157
2158 saved_zip64_limit = zipfile.ZIP64_LIMIT
2159 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2160
2161 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2162 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002163 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002164 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002165 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002166 else:
Tao Baof3282b42015-04-01 11:21:55 -07002167 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002168 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2169 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2170 # such a case (since
2171 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2172 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2173 # permission bits. We follow the logic in Python 3 to get consistent
2174 # behavior between using the two versions.
2175 if not zinfo.external_attr:
2176 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002177
2178 # If compress_type is given, it overrides the value in zinfo.
2179 if compress_type is not None:
2180 zinfo.compress_type = compress_type
2181
Tao Bao58c1b962015-05-20 09:32:18 -07002182 # If perms is given, it has a priority.
2183 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002184 # If perms doesn't set the file type, mark it as a regular file.
2185 if perms & 0o770000 == 0:
2186 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002187 zinfo.external_attr = perms << 16
2188
Tao Baof3282b42015-04-01 11:21:55 -07002189 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002190 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2191
Dan Albert8b72aef2015-03-23 19:13:21 -07002192 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002193 zipfile.ZIP64_LIMIT = saved_zip64_limit
2194
2195
Tao Bao89d7ab22017-12-14 17:05:33 -08002196def ZipDelete(zip_filename, entries):
2197 """Deletes entries from a ZIP file.
2198
2199 Since deleting entries from a ZIP file is not supported, it shells out to
2200 'zip -d'.
2201
2202 Args:
2203 zip_filename: The name of the ZIP file.
2204 entries: The name of the entry, or the list of names to be deleted.
2205
2206 Raises:
2207 AssertionError: In case of non-zero return from 'zip'.
2208 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002209 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002210 entries = [entries]
2211 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002212 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002213
2214
Tao Baof3282b42015-04-01 11:21:55 -07002215def ZipClose(zip_file):
2216 # http://b/18015246
2217 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2218 # central directory.
2219 saved_zip64_limit = zipfile.ZIP64_LIMIT
2220 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2221
2222 zip_file.close()
2223
2224 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002225
2226
2227class DeviceSpecificParams(object):
2228 module = None
2229 def __init__(self, **kwargs):
2230 """Keyword arguments to the constructor become attributes of this
2231 object, which is passed to all functions in the device-specific
2232 module."""
Tao Bao38884282019-07-10 22:20:56 -07002233 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002234 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002235 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002236
2237 if self.module is None:
2238 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002239 if not path:
2240 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002241 try:
2242 if os.path.isdir(path):
2243 info = imp.find_module("releasetools", [path])
2244 else:
2245 d, f = os.path.split(path)
2246 b, x = os.path.splitext(f)
2247 if x == ".py":
2248 f = b
2249 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002250 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002251 self.module = imp.load_module("device_specific", *info)
2252 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002253 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002254
2255 def _DoCall(self, function_name, *args, **kwargs):
2256 """Call the named function in the device-specific module, passing
2257 the given args and kwargs. The first argument to the call will be
2258 the DeviceSpecific object itself. If there is no module, or the
2259 module does not define the function, return the value of the
2260 'default' kwarg (which itself defaults to None)."""
2261 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002262 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002263 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2264
2265 def FullOTA_Assertions(self):
2266 """Called after emitting the block of assertions at the top of a
2267 full OTA package. Implementations can add whatever additional
2268 assertions they like."""
2269 return self._DoCall("FullOTA_Assertions")
2270
Doug Zongkere5ff5902012-01-17 10:55:37 -08002271 def FullOTA_InstallBegin(self):
2272 """Called at the start of full OTA installation."""
2273 return self._DoCall("FullOTA_InstallBegin")
2274
Yifan Hong10c530d2018-12-27 17:34:18 -08002275 def FullOTA_GetBlockDifferences(self):
2276 """Called during full OTA installation and verification.
2277 Implementation should return a list of BlockDifference objects describing
2278 the update on each additional partitions.
2279 """
2280 return self._DoCall("FullOTA_GetBlockDifferences")
2281
Doug Zongker05d3dea2009-06-22 11:32:31 -07002282 def FullOTA_InstallEnd(self):
2283 """Called at the end of full OTA installation; typically this is
2284 used to install the image for the device's baseband processor."""
2285 return self._DoCall("FullOTA_InstallEnd")
2286
2287 def IncrementalOTA_Assertions(self):
2288 """Called after emitting the block of assertions at the top of an
2289 incremental OTA package. Implementations can add whatever
2290 additional assertions they like."""
2291 return self._DoCall("IncrementalOTA_Assertions")
2292
Doug Zongkere5ff5902012-01-17 10:55:37 -08002293 def IncrementalOTA_VerifyBegin(self):
2294 """Called at the start of the verification phase of incremental
2295 OTA installation; additional checks can be placed here to abort
2296 the script before any changes are made."""
2297 return self._DoCall("IncrementalOTA_VerifyBegin")
2298
Doug Zongker05d3dea2009-06-22 11:32:31 -07002299 def IncrementalOTA_VerifyEnd(self):
2300 """Called at the end of the verification phase of incremental OTA
2301 installation; additional checks can be placed here to abort the
2302 script before any changes are made."""
2303 return self._DoCall("IncrementalOTA_VerifyEnd")
2304
Doug Zongkere5ff5902012-01-17 10:55:37 -08002305 def IncrementalOTA_InstallBegin(self):
2306 """Called at the start of incremental OTA installation (after
2307 verification is complete)."""
2308 return self._DoCall("IncrementalOTA_InstallBegin")
2309
Yifan Hong10c530d2018-12-27 17:34:18 -08002310 def IncrementalOTA_GetBlockDifferences(self):
2311 """Called during incremental OTA installation and verification.
2312 Implementation should return a list of BlockDifference objects describing
2313 the update on each additional partitions.
2314 """
2315 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2316
Doug Zongker05d3dea2009-06-22 11:32:31 -07002317 def IncrementalOTA_InstallEnd(self):
2318 """Called at the end of incremental OTA installation; typically
2319 this is used to install the image for the device's baseband
2320 processor."""
2321 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002322
Tao Bao9bc6bb22015-11-09 16:58:28 -08002323 def VerifyOTA_Assertions(self):
2324 return self._DoCall("VerifyOTA_Assertions")
2325
Tao Bao76def242017-11-21 09:25:31 -08002326
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002327class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002328 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002329 self.name = name
2330 self.data = data
2331 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002332 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002333 self.sha1 = sha1(data).hexdigest()
2334
2335 @classmethod
2336 def FromLocalFile(cls, name, diskname):
2337 f = open(diskname, "rb")
2338 data = f.read()
2339 f.close()
2340 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002341
2342 def WriteToTemp(self):
2343 t = tempfile.NamedTemporaryFile()
2344 t.write(self.data)
2345 t.flush()
2346 return t
2347
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002348 def WriteToDir(self, d):
2349 with open(os.path.join(d, self.name), "wb") as fp:
2350 fp.write(self.data)
2351
Geremy Condra36bd3652014-02-06 19:45:10 -08002352 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002353 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002354
Tao Bao76def242017-11-21 09:25:31 -08002355
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002356DIFF_PROGRAM_BY_EXT = {
2357 ".gz" : "imgdiff",
2358 ".zip" : ["imgdiff", "-z"],
2359 ".jar" : ["imgdiff", "-z"],
2360 ".apk" : ["imgdiff", "-z"],
2361 ".img" : "imgdiff",
2362 }
2363
Tao Bao76def242017-11-21 09:25:31 -08002364
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002365class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002366 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002367 self.tf = tf
2368 self.sf = sf
2369 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002370 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002371
2372 def ComputePatch(self):
2373 """Compute the patch (as a string of data) needed to turn sf into
2374 tf. Returns the same tuple as GetPatch()."""
2375
2376 tf = self.tf
2377 sf = self.sf
2378
Doug Zongker24cd2802012-08-14 16:36:15 -07002379 if self.diff_program:
2380 diff_program = self.diff_program
2381 else:
2382 ext = os.path.splitext(tf.name)[1]
2383 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002384
2385 ttemp = tf.WriteToTemp()
2386 stemp = sf.WriteToTemp()
2387
2388 ext = os.path.splitext(tf.name)[1]
2389
2390 try:
2391 ptemp = tempfile.NamedTemporaryFile()
2392 if isinstance(diff_program, list):
2393 cmd = copy.copy(diff_program)
2394 else:
2395 cmd = [diff_program]
2396 cmd.append(stemp.name)
2397 cmd.append(ttemp.name)
2398 cmd.append(ptemp.name)
2399 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002400 err = []
2401 def run():
2402 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002403 if e:
2404 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002405 th = threading.Thread(target=run)
2406 th.start()
2407 th.join(timeout=300) # 5 mins
2408 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002409 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002410 p.terminate()
2411 th.join(5)
2412 if th.is_alive():
2413 p.kill()
2414 th.join()
2415
Tianjie Xua2a9f992018-01-05 15:15:54 -08002416 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002417 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002418 self.patch = None
2419 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002420 diff = ptemp.read()
2421 finally:
2422 ptemp.close()
2423 stemp.close()
2424 ttemp.close()
2425
2426 self.patch = diff
2427 return self.tf, self.sf, self.patch
2428
2429
2430 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002431 """Returns a tuple of (target_file, source_file, patch_data).
2432
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002433 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002434 computing the patch failed.
2435 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002436 return self.tf, self.sf, self.patch
2437
2438
2439def ComputeDifferences(diffs):
2440 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002441 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002442
2443 # Do the largest files first, to try and reduce the long-pole effect.
2444 by_size = [(i.tf.size, i) for i in diffs]
2445 by_size.sort(reverse=True)
2446 by_size = [i[1] for i in by_size]
2447
2448 lock = threading.Lock()
2449 diff_iter = iter(by_size) # accessed under lock
2450
2451 def worker():
2452 try:
2453 lock.acquire()
2454 for d in diff_iter:
2455 lock.release()
2456 start = time.time()
2457 d.ComputePatch()
2458 dur = time.time() - start
2459 lock.acquire()
2460
2461 tf, sf, patch = d.GetPatch()
2462 if sf.name == tf.name:
2463 name = tf.name
2464 else:
2465 name = "%s (%s)" % (tf.name, sf.name)
2466 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002467 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002468 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002469 logger.info(
2470 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2471 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002472 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002473 except Exception:
2474 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002475 raise
2476
2477 # start worker threads; wait for them all to finish.
2478 threads = [threading.Thread(target=worker)
2479 for i in range(OPTIONS.worker_threads)]
2480 for th in threads:
2481 th.start()
2482 while threads:
2483 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002484
2485
Dan Albert8b72aef2015-03-23 19:13:21 -07002486class BlockDifference(object):
2487 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002488 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002489 self.tgt = tgt
2490 self.src = src
2491 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002492 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002493 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002494
Tao Baodd2a5892015-03-12 12:32:37 -07002495 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002496 version = max(
2497 int(i) for i in
2498 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002499 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002500 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002501
Tianjie Xu41976c72019-07-03 13:57:01 -07002502 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2503 version=self.version,
2504 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002505 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002506 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002507 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002508 self.touched_src_ranges = b.touched_src_ranges
2509 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002510
Yifan Hong10c530d2018-12-27 17:34:18 -08002511 # On devices with dynamic partitions, for new partitions,
2512 # src is None but OPTIONS.source_info_dict is not.
2513 if OPTIONS.source_info_dict is None:
2514 is_dynamic_build = OPTIONS.info_dict.get(
2515 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002516 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002517 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002518 is_dynamic_build = OPTIONS.source_info_dict.get(
2519 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002520 is_dynamic_source = partition in shlex.split(
2521 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002522
Yifan Hongbb2658d2019-01-25 12:30:58 -08002523 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002524 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2525
Yifan Hongbb2658d2019-01-25 12:30:58 -08002526 # For dynamic partitions builds, check partition list in both source
2527 # and target build because new partitions may be added, and existing
2528 # partitions may be removed.
2529 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2530
Yifan Hong10c530d2018-12-27 17:34:18 -08002531 if is_dynamic:
2532 self.device = 'map_partition("%s")' % partition
2533 else:
2534 if OPTIONS.source_info_dict is None:
Yifan Hongae6e0d52020-05-07 12:38:53 -07002535 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2536 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002537 else:
Yifan Hongae6e0d52020-05-07 12:38:53 -07002538 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2539 OPTIONS.source_info_dict)
2540 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002541
Tao Baod8d14be2016-02-04 14:26:02 -08002542 @property
2543 def required_cache(self):
2544 return self._required_cache
2545
Tao Bao76def242017-11-21 09:25:31 -08002546 def WriteScript(self, script, output_zip, progress=None,
2547 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002548 if not self.src:
2549 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002550 script.Print("Patching %s image unconditionally..." % (self.partition,))
2551 else:
2552 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002553
Dan Albert8b72aef2015-03-23 19:13:21 -07002554 if progress:
2555 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002556 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002557
2558 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002559 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002560
Tao Bao9bc6bb22015-11-09 16:58:28 -08002561 def WriteStrictVerifyScript(self, script):
2562 """Verify all the blocks in the care_map, including clobbered blocks.
2563
2564 This differs from the WriteVerifyScript() function: a) it prints different
2565 error messages; b) it doesn't allow half-way updated images to pass the
2566 verification."""
2567
2568 partition = self.partition
2569 script.Print("Verifying %s..." % (partition,))
2570 ranges = self.tgt.care_map
2571 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002572 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002573 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2574 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002575 self.device, ranges_str,
2576 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002577 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002578 script.AppendExtra("")
2579
Tao Baod522bdc2016-04-12 15:53:16 -07002580 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002581 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002582
2583 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002584 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002585 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002586
2587 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002588 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002589 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002590 ranges = self.touched_src_ranges
2591 expected_sha1 = self.touched_src_sha1
2592 else:
2593 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2594 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002595
2596 # No blocks to be checked, skipping.
2597 if not ranges:
2598 return
2599
Tao Bao5ece99d2015-05-12 11:42:31 -07002600 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002601 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002602 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002603 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2604 '"%s.patch.dat")) then' % (
2605 self.device, ranges_str, expected_sha1,
2606 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002607 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002608 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002609
Tianjie Xufc3422a2015-12-15 11:53:59 -08002610 if self.version >= 4:
2611
2612 # Bug: 21124327
2613 # When generating incrementals for the system and vendor partitions in
2614 # version 4 or newer, explicitly check the first block (which contains
2615 # the superblock) of the partition to see if it's what we expect. If
2616 # this check fails, give an explicit log message about the partition
2617 # having been remounted R/W (the most likely explanation).
2618 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002619 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002620
2621 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002622 if partition == "system":
2623 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2624 else:
2625 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002626 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002627 'ifelse (block_image_recover({device}, "{ranges}") && '
2628 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002629 'package_extract_file("{partition}.transfer.list"), '
2630 '"{partition}.new.dat", "{partition}.patch.dat"), '
2631 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002632 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002633 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002634 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002635
Tao Baodd2a5892015-03-12 12:32:37 -07002636 # Abort the OTA update. Note that the incremental OTA cannot be applied
2637 # even if it may match the checksum of the target partition.
2638 # a) If version < 3, operations like move and erase will make changes
2639 # unconditionally and damage the partition.
2640 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002641 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002642 if partition == "system":
2643 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2644 else:
2645 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2646 script.AppendExtra((
2647 'abort("E%d: %s partition has unexpected contents");\n'
2648 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002649
Yifan Hong10c530d2018-12-27 17:34:18 -08002650 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002651 partition = self.partition
2652 script.Print('Verifying the updated %s image...' % (partition,))
2653 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2654 ranges = self.tgt.care_map
2655 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002656 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002657 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002658 self.device, ranges_str,
2659 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002660
2661 # Bug: 20881595
2662 # Verify that extended blocks are really zeroed out.
2663 if self.tgt.extended:
2664 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002665 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002666 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002667 self.device, ranges_str,
2668 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002669 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002670 if partition == "system":
2671 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2672 else:
2673 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002674 script.AppendExtra(
2675 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002676 ' abort("E%d: %s partition has unexpected non-zero contents after '
2677 'OTA update");\n'
2678 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002679 else:
2680 script.Print('Verified the updated %s image.' % (partition,))
2681
Tianjie Xu209db462016-05-24 17:34:52 -07002682 if partition == "system":
2683 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2684 else:
2685 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2686
Tao Bao5fcaaef2015-06-01 13:40:49 -07002687 script.AppendExtra(
2688 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002689 ' abort("E%d: %s partition has unexpected contents after OTA '
2690 'update");\n'
2691 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002692
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002693 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002694 ZipWrite(output_zip,
2695 '{}.transfer.list'.format(self.path),
2696 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002697
Tao Bao76def242017-11-21 09:25:31 -08002698 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2699 # its size. Quailty 9 almost triples the compression time but doesn't
2700 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002701 # zip | brotli(quality 6) | brotli(quality 9)
2702 # compressed_size: 942M | 869M (~8% reduced) | 854M
2703 # compression_time: 75s | 265s | 719s
2704 # decompression_time: 15s | 25s | 25s
2705
2706 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002707 brotli_cmd = ['brotli', '--quality=6',
2708 '--output={}.new.dat.br'.format(self.path),
2709 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002710 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002711 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002712
2713 new_data_name = '{}.new.dat.br'.format(self.partition)
2714 ZipWrite(output_zip,
2715 '{}.new.dat.br'.format(self.path),
2716 new_data_name,
2717 compress_type=zipfile.ZIP_STORED)
2718 else:
2719 new_data_name = '{}.new.dat'.format(self.partition)
2720 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2721
Dan Albert8e0178d2015-01-27 15:53:15 -08002722 ZipWrite(output_zip,
2723 '{}.patch.dat'.format(self.path),
2724 '{}.patch.dat'.format(self.partition),
2725 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002726
Tianjie Xu209db462016-05-24 17:34:52 -07002727 if self.partition == "system":
2728 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2729 else:
2730 code = ErrorCode.VENDOR_UPDATE_FAILURE
2731
Yifan Hong10c530d2018-12-27 17:34:18 -08002732 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002733 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002734 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002735 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002736 device=self.device, partition=self.partition,
2737 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002738 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002739
Dan Albert8b72aef2015-03-23 19:13:21 -07002740 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002741 data = source.ReadRangeSet(ranges)
2742 ctx = sha1()
2743
2744 for p in data:
2745 ctx.update(p)
2746
2747 return ctx.hexdigest()
2748
Tao Baoe9b61912015-07-09 17:37:49 -07002749 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2750 """Return the hash value for all zero blocks."""
2751 zero_block = '\x00' * 4096
2752 ctx = sha1()
2753 for _ in range(num_blocks):
2754 ctx.update(zero_block)
2755
2756 return ctx.hexdigest()
2757
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002758
Tianjie Xu41976c72019-07-03 13:57:01 -07002759# Expose these two classes to support vendor-specific scripts
2760DataImage = images.DataImage
2761EmptyImage = images.EmptyImage
2762
Tao Bao76def242017-11-21 09:25:31 -08002763
Doug Zongker96a57e72010-09-26 14:57:41 -07002764# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002765PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002766 "ext4": "EMMC",
2767 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002768 "f2fs": "EMMC",
2769 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002770}
Doug Zongker96a57e72010-09-26 14:57:41 -07002771
Yifan Hongae6e0d52020-05-07 12:38:53 -07002772def GetTypeAndDevice(mount_point, info, check_no_slot=True):
2773 """
2774 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
2775 backwards compatibility. It aborts if the fstab entry has slotselect option
2776 (unless check_no_slot is explicitly set to False).
2777 """
Doug Zongker96a57e72010-09-26 14:57:41 -07002778 fstab = info["fstab"]
2779 if fstab:
Yifan Hongae6e0d52020-05-07 12:38:53 -07002780 if check_no_slot:
2781 assert not fstab[mount_point].slotselect, \
2782 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07002783 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2784 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002785 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002786 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002787
2788
Yifan Hongae6e0d52020-05-07 12:38:53 -07002789def GetTypeAndDeviceExpr(mount_point, info):
2790 """
2791 Return the filesystem of the partition, and an edify expression that evaluates
2792 to the device at runtime.
2793 """
2794 fstab = info["fstab"]
2795 if fstab:
2796 p = fstab[mount_point]
2797 device_expr = '"%s"' % fstab[mount_point].device
2798 if p.slotselect:
2799 device_expr = 'add_slot_suffix(%s)' % device_expr
2800 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
2801 else:
2802 raise KeyError
2803
2804
2805def GetEntryForDevice(fstab, device):
2806 """
2807 Returns:
2808 The first entry in fstab whose device is the given value.
2809 """
2810 if not fstab:
2811 return None
2812 for mount_point in fstab:
2813 if fstab[mount_point].device == device:
2814 return fstab[mount_point]
2815 return None
2816
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002817def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002818 """Parses and converts a PEM-encoded certificate into DER-encoded.
2819
2820 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2821
2822 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002823 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002824 """
2825 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002826 save = False
2827 for line in data.split("\n"):
2828 if "--END CERTIFICATE--" in line:
2829 break
2830 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002831 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002832 if "--BEGIN CERTIFICATE--" in line:
2833 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002834 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002835 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002836
Tao Bao04e1f012018-02-04 12:13:35 -08002837
2838def ExtractPublicKey(cert):
2839 """Extracts the public key (PEM-encoded) from the given certificate file.
2840
2841 Args:
2842 cert: The certificate filename.
2843
2844 Returns:
2845 The public key string.
2846
2847 Raises:
2848 AssertionError: On non-zero return from 'openssl'.
2849 """
2850 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2851 # While openssl 1.1 writes the key into the given filename followed by '-out',
2852 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2853 # stdout instead.
2854 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2855 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2856 pubkey, stderrdata = proc.communicate()
2857 assert proc.returncode == 0, \
2858 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2859 return pubkey
2860
2861
Tao Bao1ac886e2019-06-26 11:58:22 -07002862def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002863 """Extracts the AVB public key from the given public or private key.
2864
2865 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002866 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002867 key: The input key file, which should be PEM-encoded public or private key.
2868
2869 Returns:
2870 The path to the extracted AVB public key file.
2871 """
2872 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2873 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002874 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002875 return output
2876
2877
Doug Zongker412c02f2014-02-13 10:58:24 -08002878def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2879 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002880 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002881
Tao Bao6d5d6232018-03-09 17:04:42 -08002882 Most of the space in the boot and recovery images is just the kernel, which is
2883 identical for the two, so the resulting patch should be efficient. Add it to
2884 the output zip, along with a shell script that is run from init.rc on first
2885 boot to actually do the patching and install the new recovery image.
2886
2887 Args:
2888 input_dir: The top-level input directory of the target-files.zip.
2889 output_sink: The callback function that writes the result.
2890 recovery_img: File object for the recovery image.
2891 boot_img: File objects for the boot image.
2892 info_dict: A dict returned by common.LoadInfoDict() on the input
2893 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002894 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002895 if info_dict is None:
2896 info_dict = OPTIONS.info_dict
2897
Tao Bao6d5d6232018-03-09 17:04:42 -08002898 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002899 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2900
2901 if board_uses_vendorimage:
2902 # In this case, the output sink is rooted at VENDOR
2903 recovery_img_path = "etc/recovery.img"
2904 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2905 sh_dir = "bin"
2906 else:
2907 # In this case the output sink is rooted at SYSTEM
2908 recovery_img_path = "vendor/etc/recovery.img"
2909 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2910 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002911
Tao Baof2cffbd2015-07-22 12:33:18 -07002912 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002913 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002914
2915 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002916 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002917 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002918 # With system-root-image, boot and recovery images will have mismatching
2919 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2920 # to handle such a case.
2921 if system_root_image:
2922 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002923 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002924 assert not os.path.exists(path)
2925 else:
2926 diff_program = ["imgdiff"]
2927 if os.path.exists(path):
2928 diff_program.append("-b")
2929 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002930 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002931 else:
2932 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002933
2934 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2935 _, _, patch = d.ComputePatch()
2936 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002937
Dan Albertebb19aa2015-03-27 19:11:53 -07002938 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002939 # The following GetTypeAndDevice()s need to use the path in the target
2940 # info_dict instead of source_info_dict.
Yifan Hongae6e0d52020-05-07 12:38:53 -07002941 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
2942 check_no_slot=False)
2943 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
2944 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07002945 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002946 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002947
Tao Baof2cffbd2015-07-22 12:33:18 -07002948 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002949
2950 # Note that we use /vendor to refer to the recovery resources. This will
2951 # work for a separate vendor partition mounted at /vendor or a
2952 # /system/vendor subdirectory on the system partition, for which init will
2953 # create a symlink from /vendor to /system/vendor.
2954
2955 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002956if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2957 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002958 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002959 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2960 log -t recovery "Installing new recovery image: succeeded" || \\
2961 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002962else
2963 log -t recovery "Recovery image already installed"
2964fi
2965""" % {'type': recovery_type,
2966 'device': recovery_device,
2967 'sha1': recovery_img.sha1,
2968 'size': recovery_img.size}
2969 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002970 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002971if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2972 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002973 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002974 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2975 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2976 log -t recovery "Installing new recovery image: succeeded" || \\
2977 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002978else
2979 log -t recovery "Recovery image already installed"
2980fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002981""" % {'boot_size': boot_img.size,
2982 'boot_sha1': boot_img.sha1,
2983 'recovery_size': recovery_img.size,
2984 'recovery_sha1': recovery_img.sha1,
2985 'boot_type': boot_type,
Yifan Hongae6e0d52020-05-07 12:38:53 -07002986 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee55f62c2020-05-19 13:44:26 -07002987 'recovery_type': recovery_type,
2988 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07002989 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002990
Bill Peckhame868aec2019-09-17 17:06:47 -07002991 # The install script location moved from /system/etc to /system/bin in the L
2992 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2993 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002994
Tao Bao32fcdab2018-10-12 10:30:39 -07002995 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002996
Tao Baoda30cfa2017-12-01 16:19:46 -08002997 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002998
2999
3000class DynamicPartitionUpdate(object):
3001 def __init__(self, src_group=None, tgt_group=None, progress=None,
3002 block_difference=None):
3003 self.src_group = src_group
3004 self.tgt_group = tgt_group
3005 self.progress = progress
3006 self.block_difference = block_difference
3007
3008 @property
3009 def src_size(self):
3010 if not self.block_difference:
3011 return 0
3012 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3013
3014 @property
3015 def tgt_size(self):
3016 if not self.block_difference:
3017 return 0
3018 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3019
3020 @staticmethod
3021 def _GetSparseImageSize(img):
3022 if not img:
3023 return 0
3024 return img.blocksize * img.total_blocks
3025
3026
3027class DynamicGroupUpdate(object):
3028 def __init__(self, src_size=None, tgt_size=None):
3029 # None: group does not exist. 0: no size limits.
3030 self.src_size = src_size
3031 self.tgt_size = tgt_size
3032
3033
3034class DynamicPartitionsDifference(object):
3035 def __init__(self, info_dict, block_diffs, progress_dict=None,
3036 source_info_dict=None):
3037 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003038 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003039
3040 self._remove_all_before_apply = False
3041 if source_info_dict is None:
3042 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003043 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003044
Tao Baof1113e92019-06-18 12:10:14 -07003045 block_diff_dict = collections.OrderedDict(
3046 [(e.partition, e) for e in block_diffs])
3047
Yifan Hong10c530d2018-12-27 17:34:18 -08003048 assert len(block_diff_dict) == len(block_diffs), \
3049 "Duplicated BlockDifference object for {}".format(
3050 [partition for partition, count in
3051 collections.Counter(e.partition for e in block_diffs).items()
3052 if count > 1])
3053
Yifan Hong79997e52019-01-23 16:56:19 -08003054 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003055
3056 for p, block_diff in block_diff_dict.items():
3057 self._partition_updates[p] = DynamicPartitionUpdate()
3058 self._partition_updates[p].block_difference = block_diff
3059
3060 for p, progress in progress_dict.items():
3061 if p in self._partition_updates:
3062 self._partition_updates[p].progress = progress
3063
3064 tgt_groups = shlex.split(info_dict.get(
3065 "super_partition_groups", "").strip())
3066 src_groups = shlex.split(source_info_dict.get(
3067 "super_partition_groups", "").strip())
3068
3069 for g in tgt_groups:
3070 for p in shlex.split(info_dict.get(
3071 "super_%s_partition_list" % g, "").strip()):
3072 assert p in self._partition_updates, \
3073 "{} is in target super_{}_partition_list but no BlockDifference " \
3074 "object is provided.".format(p, g)
3075 self._partition_updates[p].tgt_group = g
3076
3077 for g in src_groups:
3078 for p in shlex.split(source_info_dict.get(
3079 "super_%s_partition_list" % g, "").strip()):
3080 assert p in self._partition_updates, \
3081 "{} is in source super_{}_partition_list but no BlockDifference " \
3082 "object is provided.".format(p, g)
3083 self._partition_updates[p].src_group = g
3084
Yifan Hong45433e42019-01-18 13:55:25 -08003085 target_dynamic_partitions = set(shlex.split(info_dict.get(
3086 "dynamic_partition_list", "").strip()))
3087 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3088 if u.tgt_size)
3089 assert block_diffs_with_target == target_dynamic_partitions, \
3090 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3091 list(target_dynamic_partitions), list(block_diffs_with_target))
3092
3093 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3094 "dynamic_partition_list", "").strip()))
3095 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3096 if u.src_size)
3097 assert block_diffs_with_source == source_dynamic_partitions, \
3098 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3099 list(source_dynamic_partitions), list(block_diffs_with_source))
3100
Yifan Hong10c530d2018-12-27 17:34:18 -08003101 if self._partition_updates:
3102 logger.info("Updating dynamic partitions %s",
3103 self._partition_updates.keys())
3104
Yifan Hong79997e52019-01-23 16:56:19 -08003105 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003106
3107 for g in tgt_groups:
3108 self._group_updates[g] = DynamicGroupUpdate()
3109 self._group_updates[g].tgt_size = int(info_dict.get(
3110 "super_%s_group_size" % g, "0").strip())
3111
3112 for g in src_groups:
3113 if g not in self._group_updates:
3114 self._group_updates[g] = DynamicGroupUpdate()
3115 self._group_updates[g].src_size = int(source_info_dict.get(
3116 "super_%s_group_size" % g, "0").strip())
3117
3118 self._Compute()
3119
3120 def WriteScript(self, script, output_zip, write_verify_script=False):
3121 script.Comment('--- Start patching dynamic partitions ---')
3122 for p, u in self._partition_updates.items():
3123 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3124 script.Comment('Patch partition %s' % p)
3125 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3126 write_verify_script=False)
3127
3128 op_list_path = MakeTempFile()
3129 with open(op_list_path, 'w') as f:
3130 for line in self._op_list:
3131 f.write('{}\n'.format(line))
3132
3133 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3134
3135 script.Comment('Update dynamic partition metadata')
3136 script.AppendExtra('assert(update_dynamic_partitions('
3137 'package_extract_file("dynamic_partitions_op_list")));')
3138
3139 if write_verify_script:
3140 for p, u in self._partition_updates.items():
3141 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3142 u.block_difference.WritePostInstallVerifyScript(script)
3143 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3144
3145 for p, u in self._partition_updates.items():
3146 if u.tgt_size and u.src_size <= u.tgt_size:
3147 script.Comment('Patch partition %s' % p)
3148 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3149 write_verify_script=write_verify_script)
3150 if write_verify_script:
3151 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3152
3153 script.Comment('--- End patching dynamic partitions ---')
3154
3155 def _Compute(self):
3156 self._op_list = list()
3157
3158 def append(line):
3159 self._op_list.append(line)
3160
3161 def comment(line):
3162 self._op_list.append("# %s" % line)
3163
3164 if self._remove_all_before_apply:
3165 comment('Remove all existing dynamic partitions and groups before '
3166 'applying full OTA')
3167 append('remove_all_groups')
3168
3169 for p, u in self._partition_updates.items():
3170 if u.src_group and not u.tgt_group:
3171 append('remove %s' % p)
3172
3173 for p, u in self._partition_updates.items():
3174 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3175 comment('Move partition %s from %s to default' % (p, u.src_group))
3176 append('move %s default' % p)
3177
3178 for p, u in self._partition_updates.items():
3179 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3180 comment('Shrink partition %s from %d to %d' %
3181 (p, u.src_size, u.tgt_size))
3182 append('resize %s %s' % (p, u.tgt_size))
3183
3184 for g, u in self._group_updates.items():
3185 if u.src_size is not None and u.tgt_size is None:
3186 append('remove_group %s' % g)
3187 if (u.src_size is not None and u.tgt_size is not None and
3188 u.src_size > u.tgt_size):
3189 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3190 append('resize_group %s %d' % (g, u.tgt_size))
3191
3192 for g, u in self._group_updates.items():
3193 if u.src_size is None and u.tgt_size is not None:
3194 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3195 append('add_group %s %d' % (g, u.tgt_size))
3196 if (u.src_size is not None and u.tgt_size is not None and
3197 u.src_size < u.tgt_size):
3198 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3199 append('resize_group %s %d' % (g, u.tgt_size))
3200
3201 for p, u in self._partition_updates.items():
3202 if u.tgt_group and not u.src_group:
3203 comment('Add partition %s to group %s' % (p, u.tgt_group))
3204 append('add %s %s' % (p, u.tgt_group))
3205
3206 for p, u in self._partition_updates.items():
3207 if u.tgt_size and u.src_size < u.tgt_size:
3208 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3209 append('resize %s %d' % (p, u.tgt_size))
3210
3211 for p, u in self._partition_updates.items():
3212 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3213 comment('Move partition %s from default to %s' %
3214 (p, u.tgt_group))
3215 append('move %s %s' % (p, u.tgt_group))