blob: d4c73a49e14e02f6ed311ab4a50a04382b75cd46 [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")
667 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800668 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700669
Tao Bao765668f2019-10-04 22:03:00 -0700670 # Load recovery fstab if applicable.
671 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800672
Tianjie Xu861f4132018-09-12 11:49:33 -0700673 # Tries to load the build props for all partitions with care_map, including
674 # system and vendor.
675 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800676 partition_prop = "{}.build.prop".format(partition)
677 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700678 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800679 # Some partition might use /<partition>/etc/build.prop as the new path.
680 # TODO: try new path first when majority of them switch to the new path.
681 if not d[partition_prop]:
682 d[partition_prop] = LoadBuildProp(
683 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700684 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800685
Tao Bao3ed35d32019-10-07 20:48:48 -0700686 # Set up the salt (based on fingerprint) that will be used when adding AVB
687 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800688 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700689 build_info = BuildInfo(d)
Daniel Normanab5acef2020-01-08 17:01:11 -0800690 for partition in PARTITIONS_WITH_CARE_MAP:
691 fingerprint = build_info.GetPartitionFingerprint(partition)
692 if fingerprint:
693 d["avb_{}_salt".format(partition)] = sha256(fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800694
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700695 return d
696
Tao Baod1de6f32017-03-01 16:38:48 -0800697
Tao Baobcd1d162017-08-26 13:10:26 -0700698def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700699 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700700 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700701 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700702 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700703 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700704 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700705
Tao Baod1de6f32017-03-01 16:38:48 -0800706
Daniel Norman4cc9df62019-07-18 10:11:07 -0700707def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900708 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700709 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900710
Daniel Norman4cc9df62019-07-18 10:11:07 -0700711
712def LoadDictionaryFromFile(file_path):
713 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900714 return LoadDictionaryFromLines(lines)
715
716
Michael Runge6e836112014-04-15 17:40:21 -0700717def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700718 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700719 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700720 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700721 if not line or line.startswith("#"):
722 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700723 if "=" in line:
724 name, value = line.split("=", 1)
725 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700726 return d
727
Tao Baod1de6f32017-03-01 16:38:48 -0800728
Tianjie Xucfa86222016-03-07 16:31:19 -0800729def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
730 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700731 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800732 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700733 self.mount_point = mount_point
734 self.fs_type = fs_type
735 self.device = device
736 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700737 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700738
739 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800740 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700741 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700742 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700743 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700744
Tao Baod1de6f32017-03-01 16:38:48 -0800745 assert fstab_version == 2
746
747 d = {}
748 for line in data.split("\n"):
749 line = line.strip()
750 if not line or line.startswith("#"):
751 continue
752
753 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
754 pieces = line.split()
755 if len(pieces) != 5:
756 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
757
758 # Ignore entries that are managed by vold.
759 options = pieces[4]
760 if "voldmanaged=" in options:
761 continue
762
763 # It's a good line, parse it.
764 length = 0
765 options = options.split(",")
766 for i in options:
767 if i.startswith("length="):
768 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800769 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800770 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700771 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800772
Tao Baod1de6f32017-03-01 16:38:48 -0800773 mount_flags = pieces[3]
774 # Honor the SELinux context if present.
775 context = None
776 for i in mount_flags.split(","):
777 if i.startswith("context="):
778 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800779
Tao Baod1de6f32017-03-01 16:38:48 -0800780 mount_point = pieces[1]
781 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
782 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800783
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700784 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700785 # system. Other areas assume system is always at "/system" so point /system
786 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700787 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800788 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700789 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700790 return d
791
792
Tao Bao765668f2019-10-04 22:03:00 -0700793def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
794 """Finds the path to recovery fstab and loads its contents."""
795 # recovery fstab is only meaningful when installing an update via recovery
796 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
797 if info_dict.get('ab_update') == 'true':
798 return None
799
800 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
801 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
802 # cases, since it may load the info_dict from an old build (e.g. when
803 # generating incremental OTAs from that build).
804 system_root_image = info_dict.get('system_root_image') == 'true'
805 if info_dict.get('no_recovery') != 'true':
806 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
807 if isinstance(input_file, zipfile.ZipFile):
808 if recovery_fstab_path not in input_file.namelist():
809 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
810 else:
811 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
812 if not os.path.exists(path):
813 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
814 return LoadRecoveryFSTab(
815 read_helper, info_dict['fstab_version'], recovery_fstab_path,
816 system_root_image)
817
818 if info_dict.get('recovery_as_boot') == 'true':
819 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
820 if isinstance(input_file, zipfile.ZipFile):
821 if recovery_fstab_path not in input_file.namelist():
822 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
823 else:
824 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
825 if not os.path.exists(path):
826 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
827 return LoadRecoveryFSTab(
828 read_helper, info_dict['fstab_version'], recovery_fstab_path,
829 system_root_image)
830
831 return None
832
833
Doug Zongker37974732010-09-16 17:44:38 -0700834def DumpInfoDict(d):
835 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700836 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700837
Dan Albert8b72aef2015-03-23 19:13:21 -0700838
Daniel Norman55417142019-11-25 16:04:36 -0800839def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700840 """Merges dynamic partition info variables.
841
842 Args:
843 framework_dict: The dictionary of dynamic partition info variables from the
844 partial framework target files.
845 vendor_dict: The dictionary of dynamic partition info variables from the
846 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700847
848 Returns:
849 The merged dynamic partition info dictionary.
850 """
851 merged_dict = {}
852 # Partition groups and group sizes are defined by the vendor dict because
853 # these values may vary for each board that uses a shared system image.
854 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800855 framework_dynamic_partition_list = framework_dict.get(
856 "dynamic_partition_list", "")
857 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
858 merged_dict["dynamic_partition_list"] = ("%s %s" % (
859 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700860 for partition_group in merged_dict["super_partition_groups"].split(" "):
861 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800862 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700863 if key not in vendor_dict:
864 raise ValueError("Vendor dict does not contain required key %s." % key)
865 merged_dict[key] = vendor_dict[key]
866
867 # Set the partition group's partition list using a concatenation of the
868 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800869 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700870 merged_dict[key] = (
871 "%s %s" %
872 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530873
874 # Pick virtual ab related flags from vendor dict, if defined.
875 if "virtual_ab" in vendor_dict.keys():
876 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
877 if "virtual_ab_retrofit" in vendor_dict.keys():
878 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700879 return merged_dict
880
881
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800882def AppendAVBSigningArgs(cmd, partition):
883 """Append signing arguments for avbtool."""
884 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
885 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700886 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
887 new_key_path = os.path.join(OPTIONS.search_path, key_path)
888 if os.path.exists(new_key_path):
889 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800890 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
891 if key_path and algorithm:
892 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700893 avb_salt = OPTIONS.info_dict.get("avb_salt")
894 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700895 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700896 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800897
898
Tao Bao765668f2019-10-04 22:03:00 -0700899def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700900 """Returns the VBMeta arguments for partition.
901
902 It sets up the VBMeta argument by including the partition descriptor from the
903 given 'image', or by configuring the partition as a chained partition.
904
905 Args:
906 partition: The name of the partition (e.g. "system").
907 image: The path to the partition image.
908 info_dict: A dict returned by common.LoadInfoDict(). Will use
909 OPTIONS.info_dict if None has been given.
910
911 Returns:
912 A list of VBMeta arguments.
913 """
914 if info_dict is None:
915 info_dict = OPTIONS.info_dict
916
917 # Check if chain partition is used.
918 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +0800919 if not key_path:
920 return ["--include_descriptors_from_image", image]
921
922 # For a non-A/B device, we don't chain /recovery nor include its descriptor
923 # into vbmeta.img. The recovery image will be configured on an independent
924 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
925 # See details at
926 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -0700927 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +0800928 return []
929
930 # Otherwise chain the partition into vbmeta.
931 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
932 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -0700933
934
Tao Bao02a08592018-07-22 12:40:45 -0700935def GetAvbChainedPartitionArg(partition, info_dict, key=None):
936 """Constructs and returns the arg to build or verify a chained partition.
937
938 Args:
939 partition: The partition name.
940 info_dict: The info dict to look up the key info and rollback index
941 location.
942 key: The key to be used for building or verifying the partition. Defaults to
943 the key listed in info_dict.
944
945 Returns:
946 A string of form "partition:rollback_index_location:key" that can be used to
947 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700948 """
949 if key is None:
950 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700951 if key and not os.path.exists(key) and OPTIONS.search_path:
952 new_key_path = os.path.join(OPTIONS.search_path, key)
953 if os.path.exists(new_key_path):
954 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700955 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700956 rollback_index_location = info_dict[
957 "avb_" + partition + "_rollback_index_location"]
958 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
959
960
Daniel Norman276f0622019-07-26 14:13:51 -0700961def BuildVBMeta(image_path, partitions, name, needed_partitions):
962 """Creates a VBMeta image.
963
964 It generates the requested VBMeta image. The requested image could be for
965 top-level or chained VBMeta image, which is determined based on the name.
966
967 Args:
968 image_path: The output path for the new VBMeta image.
969 partitions: A dict that's keyed by partition names with image paths as
970 values. Only valid partition names are accepted, as listed in
971 common.AVB_PARTITIONS.
972 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
973 needed_partitions: Partitions whose descriptors should be included into the
974 generated VBMeta image.
975
976 Raises:
977 AssertionError: On invalid input args.
978 """
979 avbtool = OPTIONS.info_dict["avb_avbtool"]
980 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
981 AppendAVBSigningArgs(cmd, name)
982
983 for partition, path in partitions.items():
984 if partition not in needed_partitions:
985 continue
986 assert (partition in AVB_PARTITIONS or
987 partition in AVB_VBMETA_PARTITIONS), \
988 'Unknown partition: {}'.format(partition)
989 assert os.path.exists(path), \
990 'Failed to find {} for {}'.format(path, partition)
991 cmd.extend(GetAvbPartitionArg(partition, path))
992
993 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
994 if args and args.strip():
995 split_args = shlex.split(args)
996 for index, arg in enumerate(split_args[:-1]):
997 # Sanity check that the image file exists. Some images might be defined
998 # as a path relative to source tree, which may not be available at the
999 # same location when running this script (we have the input target_files
1000 # zip only). For such cases, we additionally scan other locations (e.g.
1001 # IMAGES/, RADIO/, etc) before bailing out.
1002 if arg == '--include_descriptors_from_image':
1003 image_path = split_args[index + 1]
1004 if os.path.exists(image_path):
1005 continue
1006 found = False
1007 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1008 alt_path = os.path.join(
1009 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
1010 if os.path.exists(alt_path):
1011 split_args[index + 1] = alt_path
1012 found = True
1013 break
1014 assert found, 'Failed to find {}'.format(image_path)
1015 cmd.extend(split_args)
1016
1017 RunAndCheckOutput(cmd)
1018
Dan Austin52903642019-12-12 15:44:00 -08001019 if OPTIONS.aftl_server is not None:
1020 # Ensure the other AFTL parameters are set as well.
1021 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1022 assert OPTIONS.aftl_manufacturer_key_path is not None, 'No AFTL manufacturer key provided.'
1023 assert OPTIONS.aftl_signer_helper is not None, 'No AFTL signer helper provided.'
1024 # AFTL inclusion proof generation code will go here.
Daniel Norman276f0622019-07-26 14:13:51 -07001025
Steve Mucklee1b10862019-07-10 10:49:37 -07001026def _MakeRamdisk(sourcedir, fs_config_file=None):
1027 ramdisk_img = tempfile.NamedTemporaryFile()
1028
1029 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1030 cmd = ["mkbootfs", "-f", fs_config_file,
1031 os.path.join(sourcedir, "RAMDISK")]
1032 else:
1033 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1034 p1 = Run(cmd, stdout=subprocess.PIPE)
1035 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1036
1037 p2.wait()
1038 p1.wait()
1039 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1040 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1041
1042 return ramdisk_img
1043
1044
Steve Mucklef83e3c32020-04-08 18:27:00 -07001045def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001046 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001047 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001048
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001049 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001050 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1051 we are building a two-step special image (i.e. building a recovery image to
1052 be loaded into /boot in two-step OTAs).
1053
1054 Return the image data, or None if sourcedir does not appear to contains files
1055 for building the requested image.
1056 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001057
Steve Mucklef83e3c32020-04-08 18:27:00 -07001058 # "boot" or "recovery", without extension.
1059 partition_name = os.path.basename(sourcedir).lower()
1060
1061 if partition_name == "recovery":
1062 kernel = "kernel"
1063 else:
1064 kernel = image_name.replace("boot", "kernel")
1065 kernel = kernel.replace(".img","")
1066 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001067 return None
1068
1069 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001070 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001071
Doug Zongkerd5131602012-08-02 14:46:42 -07001072 if info_dict is None:
1073 info_dict = OPTIONS.info_dict
1074
Doug Zongkereef39442009-04-02 12:14:19 -07001075 img = tempfile.NamedTemporaryFile()
1076
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001077 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001078 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001079
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001080 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1081 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1082
Steve Mucklef83e3c32020-04-08 18:27:00 -07001083 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001084
Benoit Fradina45a8682014-07-14 21:00:43 +02001085 fn = os.path.join(sourcedir, "second")
1086 if os.access(fn, os.F_OK):
1087 cmd.append("--second")
1088 cmd.append(fn)
1089
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001090 fn = os.path.join(sourcedir, "dtb")
1091 if os.access(fn, os.F_OK):
1092 cmd.append("--dtb")
1093 cmd.append(fn)
1094
Doug Zongker171f1cd2009-06-15 22:36:37 -07001095 fn = os.path.join(sourcedir, "cmdline")
1096 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001097 cmd.append("--cmdline")
1098 cmd.append(open(fn).read().rstrip("\n"))
1099
1100 fn = os.path.join(sourcedir, "base")
1101 if os.access(fn, os.F_OK):
1102 cmd.append("--base")
1103 cmd.append(open(fn).read().rstrip("\n"))
1104
Ying Wang4de6b5b2010-08-25 14:29:34 -07001105 fn = os.path.join(sourcedir, "pagesize")
1106 if os.access(fn, os.F_OK):
1107 cmd.append("--pagesize")
1108 cmd.append(open(fn).read().rstrip("\n"))
1109
Steve Muckle759d0c82020-03-16 19:13:46 -07001110 if partition_name == "recovery":
1111 args = info_dict.get("recovery_mkbootimg_args")
1112 else:
1113 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001114 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001115 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001116
Tao Bao76def242017-11-21 09:25:31 -08001117 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001118 if args and args.strip():
1119 cmd.extend(shlex.split(args))
1120
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001121 if has_ramdisk:
1122 cmd.extend(["--ramdisk", ramdisk_img.name])
1123
Tao Baod95e9fd2015-03-29 23:07:41 -07001124 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001125 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001126 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001127 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001128 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001129 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001130
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001131 if partition_name == "recovery":
1132 if info_dict.get("include_recovery_dtbo") == "true":
1133 fn = os.path.join(sourcedir, "recovery_dtbo")
1134 cmd.extend(["--recovery_dtbo", fn])
1135 if info_dict.get("include_recovery_acpio") == "true":
1136 fn = os.path.join(sourcedir, "recovery_acpio")
1137 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001138
Tao Bao986ee862018-10-04 15:46:16 -07001139 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001140
Tao Bao76def242017-11-21 09:25:31 -08001141 if (info_dict.get("boot_signer") == "true" and
1142 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001143 # Hard-code the path as "/boot" for two-step special recovery image (which
1144 # will be loaded into /boot during the two-step OTA).
1145 if two_step_image:
1146 path = "/boot"
1147 else:
Tao Baobf70c312017-07-11 17:27:55 -07001148 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001149 cmd = [OPTIONS.boot_signer_path]
1150 cmd.extend(OPTIONS.boot_signer_args)
1151 cmd.extend([path, img.name,
1152 info_dict["verity_key"] + ".pk8",
1153 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001154 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001155
Tao Baod95e9fd2015-03-29 23:07:41 -07001156 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001157 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001158 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001159 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001160 # We have switched from the prebuilt futility binary to using the tool
1161 # (futility-host) built from the source. Override the setting in the old
1162 # TF.zip.
1163 futility = info_dict["futility"]
1164 if futility.startswith("prebuilts/"):
1165 futility = "futility-host"
1166 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001167 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001168 info_dict["vboot_key"] + ".vbprivk",
1169 info_dict["vboot_subkey"] + ".vbprivk",
1170 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001171 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001172 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001173
Tao Baof3282b42015-04-01 11:21:55 -07001174 # Clean up the temp files.
1175 img_unsigned.close()
1176 img_keyblock.close()
1177
David Zeuthen8fecb282017-12-01 16:24:01 -05001178 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001179 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001180 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001181 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001182 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001183 "--partition_size", str(part_size), "--partition_name",
1184 partition_name]
1185 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001186 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001187 if args and args.strip():
1188 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001189 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001190
1191 img.seek(os.SEEK_SET, 0)
1192 data = img.read()
1193
1194 if has_ramdisk:
1195 ramdisk_img.close()
1196 img.close()
1197
1198 return data
1199
1200
Doug Zongkerd5131602012-08-02 14:46:42 -07001201def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001202 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001203 """Return a File object with the desired bootable image.
1204
1205 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1206 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1207 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001208
Doug Zongker55d93282011-01-25 17:03:34 -08001209 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1210 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001211 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001212 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001213
1214 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1215 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001216 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001217 return File.FromLocalFile(name, prebuilt_path)
1218
Tao Bao32fcdab2018-10-12 10:30:39 -07001219 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001220
1221 if info_dict is None:
1222 info_dict = OPTIONS.info_dict
1223
1224 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001225 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1226 # for recovery.
1227 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1228 prebuilt_name != "boot.img" or
1229 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001230
Doug Zongker6f1d0312014-08-22 08:07:12 -07001231 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Mucklef83e3c32020-04-08 18:27:00 -07001232 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001233 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001234 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001235 if data:
1236 return File(name, data)
1237 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001238
Doug Zongkereef39442009-04-02 12:14:19 -07001239
Steve Mucklee1b10862019-07-10 10:49:37 -07001240def _BuildVendorBootImage(sourcedir, info_dict=None):
1241 """Build a vendor boot image from the specified sourcedir.
1242
1243 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1244 turn them into a vendor boot image.
1245
1246 Return the image data, or None if sourcedir does not appear to contains files
1247 for building the requested image.
1248 """
1249
1250 if info_dict is None:
1251 info_dict = OPTIONS.info_dict
1252
1253 img = tempfile.NamedTemporaryFile()
1254
1255 ramdisk_img = _MakeRamdisk(sourcedir)
1256
1257 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1258 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1259
1260 cmd = [mkbootimg]
1261
1262 fn = os.path.join(sourcedir, "dtb")
1263 if os.access(fn, os.F_OK):
1264 cmd.append("--dtb")
1265 cmd.append(fn)
1266
1267 fn = os.path.join(sourcedir, "vendor_cmdline")
1268 if os.access(fn, os.F_OK):
1269 cmd.append("--vendor_cmdline")
1270 cmd.append(open(fn).read().rstrip("\n"))
1271
1272 fn = os.path.join(sourcedir, "base")
1273 if os.access(fn, os.F_OK):
1274 cmd.append("--base")
1275 cmd.append(open(fn).read().rstrip("\n"))
1276
1277 fn = os.path.join(sourcedir, "pagesize")
1278 if os.access(fn, os.F_OK):
1279 cmd.append("--pagesize")
1280 cmd.append(open(fn).read().rstrip("\n"))
1281
1282 args = info_dict.get("mkbootimg_args")
1283 if args and args.strip():
1284 cmd.extend(shlex.split(args))
1285
1286 args = info_dict.get("mkbootimg_version_args")
1287 if args and args.strip():
1288 cmd.extend(shlex.split(args))
1289
1290 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1291 cmd.extend(["--vendor_boot", img.name])
1292
1293 RunAndCheckOutput(cmd)
1294
1295 # AVB: if enabled, calculate and add hash.
1296 if info_dict.get("avb_enable") == "true":
1297 avbtool = info_dict["avb_avbtool"]
1298 part_size = info_dict["vendor_boot_size"]
1299 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001300 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001301 AppendAVBSigningArgs(cmd, "vendor_boot")
1302 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1303 if args and args.strip():
1304 cmd.extend(shlex.split(args))
1305 RunAndCheckOutput(cmd)
1306
1307 img.seek(os.SEEK_SET, 0)
1308 data = img.read()
1309
1310 ramdisk_img.close()
1311 img.close()
1312
1313 return data
1314
1315
1316def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1317 info_dict=None):
1318 """Return a File object with the desired vendor boot image.
1319
1320 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1321 the source files in 'unpack_dir'/'tree_subdir'."""
1322
1323 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1324 if os.path.exists(prebuilt_path):
1325 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1326 return File.FromLocalFile(name, prebuilt_path)
1327
1328 logger.info("building image from target_files %s...", tree_subdir)
1329
1330 if info_dict is None:
1331 info_dict = OPTIONS.info_dict
1332
1333 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1334 if data:
1335 return File(name, data)
1336 return None
1337
1338
Narayan Kamatha07bf042017-08-14 14:49:21 +01001339def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001340 """Gunzips the given gzip compressed file to a given output file."""
1341 with gzip.open(in_filename, "rb") as in_file, \
1342 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001343 shutil.copyfileobj(in_file, out_file)
1344
1345
Tao Bao0ff15de2019-03-20 11:26:06 -07001346def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001347 """Unzips the archive to the given directory.
1348
1349 Args:
1350 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001351 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001352 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1353 archvie. Non-matching patterns will be filtered out. If there's no match
1354 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001355 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001356 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001357 if patterns is not None:
1358 # Filter out non-matching patterns. unzip will complain otherwise.
1359 with zipfile.ZipFile(filename) as input_zip:
1360 names = input_zip.namelist()
1361 filtered = [
1362 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1363
1364 # There isn't any matching files. Don't unzip anything.
1365 if not filtered:
1366 return
1367 cmd.extend(filtered)
1368
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001369 RunAndCheckOutput(cmd)
1370
1371
Doug Zongker75f17362009-12-08 13:46:44 -08001372def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001373 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001374
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001375 Args:
1376 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1377 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1378
1379 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1380 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001381
Tao Bao1c830bf2017-12-25 10:43:47 -08001382 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001383 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001384 """
Doug Zongkereef39442009-04-02 12:14:19 -07001385
Tao Bao1c830bf2017-12-25 10:43:47 -08001386 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001387 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1388 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001389 UnzipToDir(m.group(1), tmp, pattern)
1390 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001391 filename = m.group(1)
1392 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001393 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001394
Tao Baodba59ee2018-01-09 13:21:02 -08001395 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001396
1397
Yifan Hong8a66a712019-04-04 15:37:57 -07001398def GetUserImage(which, tmpdir, input_zip,
1399 info_dict=None,
1400 allow_shared_blocks=None,
1401 hashtree_info_generator=None,
1402 reset_file_map=False):
1403 """Returns an Image object suitable for passing to BlockImageDiff.
1404
1405 This function loads the specified image from the given path. If the specified
1406 image is sparse, it also performs additional processing for OTA purpose. For
1407 example, it always adds block 0 to clobbered blocks list. It also detects
1408 files that cannot be reconstructed from the block list, for whom we should
1409 avoid applying imgdiff.
1410
1411 Args:
1412 which: The partition name.
1413 tmpdir: The directory that contains the prebuilt image and block map file.
1414 input_zip: The target-files ZIP archive.
1415 info_dict: The dict to be looked up for relevant info.
1416 allow_shared_blocks: If image is sparse, whether having shared blocks is
1417 allowed. If none, it is looked up from info_dict.
1418 hashtree_info_generator: If present and image is sparse, generates the
1419 hashtree_info for this sparse image.
1420 reset_file_map: If true and image is sparse, reset file map before returning
1421 the image.
1422 Returns:
1423 A Image object. If it is a sparse image and reset_file_map is False, the
1424 image will have file_map info loaded.
1425 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001426 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001427 info_dict = LoadInfoDict(input_zip)
1428
1429 is_sparse = info_dict.get("extfs_sparse_flag")
1430
1431 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1432 # shared blocks (i.e. some blocks will show up in multiple files' block
1433 # list). We can only allocate such shared blocks to the first "owner", and
1434 # disable imgdiff for all later occurrences.
1435 if allow_shared_blocks is None:
1436 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1437
1438 if is_sparse:
1439 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1440 hashtree_info_generator)
1441 if reset_file_map:
1442 img.ResetFileMap()
1443 return img
1444 else:
1445 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1446
1447
1448def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1449 """Returns a Image object suitable for passing to BlockImageDiff.
1450
1451 This function loads the specified non-sparse image from the given path.
1452
1453 Args:
1454 which: The partition name.
1455 tmpdir: The directory that contains the prebuilt image and block map file.
1456 Returns:
1457 A Image object.
1458 """
1459 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1460 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1461
1462 # The image and map files must have been created prior to calling
1463 # ota_from_target_files.py (since LMP).
1464 assert os.path.exists(path) and os.path.exists(mappath)
1465
Tianjie Xu41976c72019-07-03 13:57:01 -07001466 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1467
Yifan Hong8a66a712019-04-04 15:37:57 -07001468
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001469def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1470 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001471 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1472
1473 This function loads the specified sparse image from the given path, and
1474 performs additional processing for OTA purpose. For example, it always adds
1475 block 0 to clobbered blocks list. It also detects files that cannot be
1476 reconstructed from the block list, for whom we should avoid applying imgdiff.
1477
1478 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001479 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001480 tmpdir: The directory that contains the prebuilt image and block map file.
1481 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001482 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001483 hashtree_info_generator: If present, generates the hashtree_info for this
1484 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001485 Returns:
1486 A SparseImage object, with file_map info loaded.
1487 """
Tao Baoc765cca2018-01-31 17:32:40 -08001488 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1489 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1490
1491 # The image and map files must have been created prior to calling
1492 # ota_from_target_files.py (since LMP).
1493 assert os.path.exists(path) and os.path.exists(mappath)
1494
1495 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1496 # it to clobbered_blocks so that it will be written to the target
1497 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1498 clobbered_blocks = "0"
1499
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001500 image = sparse_img.SparseImage(
1501 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1502 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001503
1504 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1505 # if they contain all zeros. We can't reconstruct such a file from its block
1506 # list. Tag such entries accordingly. (Bug: 65213616)
1507 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001508 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001509 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001510 continue
1511
Tom Cherryd14b8952018-08-09 14:26:00 -07001512 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1513 # filename listed in system.map may contain an additional leading slash
1514 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1515 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001516 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001517
Tom Cherryd14b8952018-08-09 14:26:00 -07001518 # Special handling another case, where files not under /system
1519 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001520 if which == 'system' and not arcname.startswith('SYSTEM'):
1521 arcname = 'ROOT/' + arcname
1522
1523 assert arcname in input_zip.namelist(), \
1524 "Failed to find the ZIP entry for {}".format(entry)
1525
Tao Baoc765cca2018-01-31 17:32:40 -08001526 info = input_zip.getinfo(arcname)
1527 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001528
1529 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001530 # image, check the original block list to determine its completeness. Note
1531 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001532 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001533 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001534
Tao Baoc765cca2018-01-31 17:32:40 -08001535 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1536 ranges.extra['incomplete'] = True
1537
1538 return image
1539
1540
Doug Zongkereef39442009-04-02 12:14:19 -07001541def GetKeyPasswords(keylist):
1542 """Given a list of keys, prompt the user to enter passwords for
1543 those which require them. Return a {key: password} dict. password
1544 will be None if the key has no password."""
1545
Doug Zongker8ce7c252009-05-22 13:34:54 -07001546 no_passwords = []
1547 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001548 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001549 devnull = open("/dev/null", "w+b")
1550 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001551 # We don't need a password for things that aren't really keys.
1552 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001553 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001554 continue
1555
T.R. Fullhart37e10522013-03-18 10:31:26 -07001556 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001557 "-inform", "DER", "-nocrypt"],
1558 stdin=devnull.fileno(),
1559 stdout=devnull.fileno(),
1560 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001561 p.communicate()
1562 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001563 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001564 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001565 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001566 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1567 "-inform", "DER", "-passin", "pass:"],
1568 stdin=devnull.fileno(),
1569 stdout=devnull.fileno(),
1570 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001571 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001572 if p.returncode == 0:
1573 # Encrypted key with empty string as password.
1574 key_passwords[k] = ''
1575 elif stderr.startswith('Error decrypting key'):
1576 # Definitely encrypted key.
1577 # It would have said "Error reading key" if it didn't parse correctly.
1578 need_passwords.append(k)
1579 else:
1580 # Potentially, a type of key that openssl doesn't understand.
1581 # We'll let the routines in signapk.jar handle it.
1582 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001583 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001584
T.R. Fullhart37e10522013-03-18 10:31:26 -07001585 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001586 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001587 return key_passwords
1588
1589
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001590def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001591 """Gets the minSdkVersion declared in the APK.
1592
changho.shin0f125362019-07-08 10:59:00 +09001593 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001594 This can be both a decimal number (API Level) or a codename.
1595
1596 Args:
1597 apk_name: The APK filename.
1598
1599 Returns:
1600 The parsed SDK version string.
1601
1602 Raises:
1603 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001604 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001605 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001606 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001607 stderr=subprocess.PIPE)
1608 stdoutdata, stderrdata = proc.communicate()
1609 if proc.returncode != 0:
1610 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001611 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001612 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001613
Tao Baof47bf0f2018-03-21 23:28:51 -07001614 for line in stdoutdata.split("\n"):
1615 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001616 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1617 if m:
1618 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001619 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001620
1621
1622def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001623 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001624
Tao Baof47bf0f2018-03-21 23:28:51 -07001625 If minSdkVersion is set to a codename, it is translated to a number using the
1626 provided map.
1627
1628 Args:
1629 apk_name: The APK filename.
1630
1631 Returns:
1632 The parsed SDK version number.
1633
1634 Raises:
1635 ExternalError: On failing to get the min SDK version number.
1636 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001637 version = GetMinSdkVersion(apk_name)
1638 try:
1639 return int(version)
1640 except ValueError:
1641 # Not a decimal number. Codename?
1642 if version in codename_to_api_level_map:
1643 return codename_to_api_level_map[version]
1644 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001645 raise ExternalError(
1646 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1647 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001648
1649
1650def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001651 codename_to_api_level_map=None, whole_file=False,
1652 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001653 """Sign the input_name zip/jar/apk, producing output_name. Use the
1654 given key and password (the latter may be None if the key does not
1655 have a password.
1656
Doug Zongker951495f2009-08-14 12:44:19 -07001657 If whole_file is true, use the "-w" option to SignApk to embed a
1658 signature that covers the whole file in the archive comment of the
1659 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001660
1661 min_api_level is the API Level (int) of the oldest platform this file may end
1662 up on. If not specified for an APK, the API Level is obtained by interpreting
1663 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1664
1665 codename_to_api_level_map is needed to translate the codename which may be
1666 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001667
1668 Caller may optionally specify extra args to be passed to SignApk, which
1669 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001670 """
Tao Bao76def242017-11-21 09:25:31 -08001671 if codename_to_api_level_map is None:
1672 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001673 if extra_signapk_args is None:
1674 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001675
Alex Klyubin9667b182015-12-10 13:38:50 -08001676 java_library_path = os.path.join(
1677 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1678
Tao Baoe95540e2016-11-08 12:08:53 -08001679 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1680 ["-Djava.library.path=" + java_library_path,
1681 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001682 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001683 if whole_file:
1684 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001685
1686 min_sdk_version = min_api_level
1687 if min_sdk_version is None:
1688 if not whole_file:
1689 min_sdk_version = GetMinSdkVersionInt(
1690 input_name, codename_to_api_level_map)
1691 if min_sdk_version is not None:
1692 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1693
T.R. Fullhart37e10522013-03-18 10:31:26 -07001694 cmd.extend([key + OPTIONS.public_key_suffix,
1695 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001696 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001697
Tao Bao73dd4f42018-10-04 16:25:33 -07001698 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001699 if password is not None:
1700 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001701 stdoutdata, _ = proc.communicate(password)
1702 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001703 raise ExternalError(
1704 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001705 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001706
Doug Zongkereef39442009-04-02 12:14:19 -07001707
Doug Zongker37974732010-09-16 17:44:38 -07001708def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001709 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001710
Tao Bao9dd909e2017-11-14 11:27:32 -08001711 For non-AVB images, raise exception if the data is too big. Print a warning
1712 if the data is nearing the maximum size.
1713
1714 For AVB images, the actual image size should be identical to the limit.
1715
1716 Args:
1717 data: A string that contains all the data for the partition.
1718 target: The partition name. The ".img" suffix is optional.
1719 info_dict: The dict to be looked up for relevant info.
1720 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001721 if target.endswith(".img"):
1722 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001723 mount_point = "/" + target
1724
Ying Wangf8824af2014-06-03 14:07:27 -07001725 fs_type = None
1726 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001727 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001728 if mount_point == "/userdata":
1729 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001730 p = info_dict["fstab"][mount_point]
1731 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001732 device = p.device
1733 if "/" in device:
1734 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001735 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001736 if not fs_type or not limit:
1737 return
Doug Zongkereef39442009-04-02 12:14:19 -07001738
Andrew Boie0f9aec82012-02-14 09:32:52 -08001739 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001740 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1741 # path.
1742 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1743 if size != limit:
1744 raise ExternalError(
1745 "Mismatching image size for %s: expected %d actual %d" % (
1746 target, limit, size))
1747 else:
1748 pct = float(size) * 100.0 / limit
1749 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1750 if pct >= 99.0:
1751 raise ExternalError(msg)
1752 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001753 logger.warning("\n WARNING: %s\n", msg)
1754 else:
1755 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001756
1757
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001758def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001759 """Parses the APK certs info from a given target-files zip.
1760
1761 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1762 tuple with the following elements: (1) a dictionary that maps packages to
1763 certs (based on the "certificate" and "private_key" attributes in the file;
1764 (2) a string representing the extension of compressed APKs in the target files
1765 (e.g ".gz", ".bro").
1766
1767 Args:
1768 tf_zip: The input target_files ZipFile (already open).
1769
1770 Returns:
1771 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1772 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1773 no compressed APKs.
1774 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001775 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001776 compressed_extension = None
1777
Tao Bao0f990332017-09-08 19:02:54 -07001778 # META/apkcerts.txt contains the info for _all_ the packages known at build
1779 # time. Filter out the ones that are not installed.
1780 installed_files = set()
1781 for name in tf_zip.namelist():
1782 basename = os.path.basename(name)
1783 if basename:
1784 installed_files.add(basename)
1785
Tao Baoda30cfa2017-12-01 16:19:46 -08001786 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001787 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001788 if not line:
1789 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001790 m = re.match(
1791 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham96c9e6e2020-04-03 15:36:23 -07001792 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1793 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001794 line)
1795 if not m:
1796 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001797
Tao Bao818ddf52018-01-05 11:17:34 -08001798 matches = m.groupdict()
1799 cert = matches["CERT"]
1800 privkey = matches["PRIVKEY"]
1801 name = matches["NAME"]
1802 this_compressed_extension = matches["COMPRESSED"]
1803
1804 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1805 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1806 if cert in SPECIAL_CERT_STRINGS and not privkey:
1807 certmap[name] = cert
1808 elif (cert.endswith(OPTIONS.public_key_suffix) and
1809 privkey.endswith(OPTIONS.private_key_suffix) and
1810 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1811 certmap[name] = cert[:-public_key_suffix_len]
1812 else:
1813 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1814
1815 if not this_compressed_extension:
1816 continue
1817
1818 # Only count the installed files.
1819 filename = name + '.' + this_compressed_extension
1820 if filename not in installed_files:
1821 continue
1822
1823 # Make sure that all the values in the compression map have the same
1824 # extension. We don't support multiple compression methods in the same
1825 # system image.
1826 if compressed_extension:
1827 if this_compressed_extension != compressed_extension:
1828 raise ValueError(
1829 "Multiple compressed extensions: {} vs {}".format(
1830 compressed_extension, this_compressed_extension))
1831 else:
1832 compressed_extension = this_compressed_extension
1833
1834 return (certmap,
1835 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001836
1837
Doug Zongkereef39442009-04-02 12:14:19 -07001838COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001839Global options
1840
1841 -p (--path) <dir>
1842 Prepend <dir>/bin to the list of places to search for binaries run by this
1843 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001844
Doug Zongker05d3dea2009-06-22 11:32:31 -07001845 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001846 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001847
Tao Bao30df8b42018-04-23 15:32:53 -07001848 -x (--extra) <key=value>
1849 Add a key/value pair to the 'extras' dict, which device-specific extension
1850 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001851
Doug Zongkereef39442009-04-02 12:14:19 -07001852 -v (--verbose)
1853 Show command lines being executed.
1854
1855 -h (--help)
1856 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001857
1858 --logfile <file>
1859 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001860"""
1861
1862def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001863 print(docstring.rstrip("\n"))
1864 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001865
1866
1867def ParseOptions(argv,
1868 docstring,
1869 extra_opts="", extra_long_opts=(),
1870 extra_option_handler=None):
1871 """Parse the options in argv and return any arguments that aren't
1872 flags. docstring is the calling module's docstring, to be displayed
1873 for errors and -h. extra_opts and extra_long_opts are for flags
1874 defined by the caller, which are processed by passing them to
1875 extra_option_handler."""
1876
1877 try:
1878 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001879 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001880 ["help", "verbose", "path=", "signapk_path=",
1881 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08001882 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001883 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1884 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Dan Austin52903642019-12-12 15:44:00 -08001885 "extra=", "logfile=", "aftl_server=", "aftl_key_path=",
1886 "aftl_manufacturer_key_path=", "aftl_signer_helper="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001887 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001888 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001889 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001890 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001891 sys.exit(2)
1892
Doug Zongkereef39442009-04-02 12:14:19 -07001893 for o, a in opts:
1894 if o in ("-h", "--help"):
1895 Usage(docstring)
1896 sys.exit()
1897 elif o in ("-v", "--verbose"):
1898 OPTIONS.verbose = True
1899 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001900 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001901 elif o in ("--signapk_path",):
1902 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001903 elif o in ("--signapk_shared_library_path",):
1904 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001905 elif o in ("--extra_signapk_args",):
1906 OPTIONS.extra_signapk_args = shlex.split(a)
1907 elif o in ("--java_path",):
1908 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001909 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001910 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08001911 elif o in ("--android_jar_path",):
1912 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001913 elif o in ("--public_key_suffix",):
1914 OPTIONS.public_key_suffix = a
1915 elif o in ("--private_key_suffix",):
1916 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001917 elif o in ("--boot_signer_path",):
1918 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001919 elif o in ("--boot_signer_args",):
1920 OPTIONS.boot_signer_args = shlex.split(a)
1921 elif o in ("--verity_signer_path",):
1922 OPTIONS.verity_signer_path = a
1923 elif o in ("--verity_signer_args",):
1924 OPTIONS.verity_signer_args = shlex.split(a)
Dan Austin52903642019-12-12 15:44:00 -08001925 elif o in ("--aftl_server",):
1926 OPTIONS.aftl_server = a
1927 elif o in ("--aftl_key_path",):
1928 OPTIONS.aftl_key_path = a
1929 elif o in ("--aftl_manufacturer_key_path",):
1930 OPTIONS.aftl_manufacturer_key_path = a
1931 elif o in ("--aftl_signer_helper",):
1932 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07001933 elif o in ("-s", "--device_specific"):
1934 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001935 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001936 key, value = a.split("=", 1)
1937 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001938 elif o in ("--logfile",):
1939 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001940 else:
1941 if extra_option_handler is None or not extra_option_handler(o, a):
1942 assert False, "unknown option \"%s\"" % (o,)
1943
Doug Zongker85448772014-09-09 14:59:20 -07001944 if OPTIONS.search_path:
1945 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1946 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001947
1948 return args
1949
1950
Tao Bao4c851b12016-09-19 13:54:38 -07001951def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001952 """Make a temp file and add it to the list of things to be deleted
1953 when Cleanup() is called. Return the filename."""
1954 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1955 os.close(fd)
1956 OPTIONS.tempfiles.append(fn)
1957 return fn
1958
1959
Tao Bao1c830bf2017-12-25 10:43:47 -08001960def MakeTempDir(prefix='tmp', suffix=''):
1961 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1962
1963 Returns:
1964 The absolute pathname of the new directory.
1965 """
1966 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1967 OPTIONS.tempfiles.append(dir_name)
1968 return dir_name
1969
1970
Doug Zongkereef39442009-04-02 12:14:19 -07001971def Cleanup():
1972 for i in OPTIONS.tempfiles:
1973 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001974 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001975 else:
1976 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001977 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001978
1979
1980class PasswordManager(object):
1981 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001982 self.editor = os.getenv("EDITOR")
1983 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001984
1985 def GetPasswords(self, items):
1986 """Get passwords corresponding to each string in 'items',
1987 returning a dict. (The dict may have keys in addition to the
1988 values in 'items'.)
1989
1990 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1991 user edit that file to add more needed passwords. If no editor is
1992 available, or $ANDROID_PW_FILE isn't define, prompts the user
1993 interactively in the ordinary way.
1994 """
1995
1996 current = self.ReadFile()
1997
1998 first = True
1999 while True:
2000 missing = []
2001 for i in items:
2002 if i not in current or not current[i]:
2003 missing.append(i)
2004 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002005 if not missing:
2006 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002007
2008 for i in missing:
2009 current[i] = ""
2010
2011 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002012 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002013 if sys.version_info[0] >= 3:
2014 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002015 answer = raw_input("try to edit again? [y]> ").strip()
2016 if answer and answer[0] not in 'yY':
2017 raise RuntimeError("key passwords unavailable")
2018 first = False
2019
2020 current = self.UpdateAndReadFile(current)
2021
Dan Albert8b72aef2015-03-23 19:13:21 -07002022 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002023 """Prompt the user to enter a value (password) for each key in
2024 'current' whose value is fales. Returns a new dict with all the
2025 values.
2026 """
2027 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002028 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002029 if v:
2030 result[k] = v
2031 else:
2032 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002033 result[k] = getpass.getpass(
2034 "Enter password for %s key> " % k).strip()
2035 if result[k]:
2036 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002037 return result
2038
2039 def UpdateAndReadFile(self, current):
2040 if not self.editor or not self.pwfile:
2041 return self.PromptResult(current)
2042
2043 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002044 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002045 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2046 f.write("# (Additional spaces are harmless.)\n\n")
2047
2048 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002049 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002050 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002051 f.write("[[[ %s ]]] %s\n" % (v, k))
2052 if not v and first_line is None:
2053 # position cursor on first line with no password.
2054 first_line = i + 4
2055 f.close()
2056
Tao Bao986ee862018-10-04 15:46:16 -07002057 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002058
2059 return self.ReadFile()
2060
2061 def ReadFile(self):
2062 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002063 if self.pwfile is None:
2064 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002065 try:
2066 f = open(self.pwfile, "r")
2067 for line in f:
2068 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002069 if not line or line[0] == '#':
2070 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002071 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2072 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002073 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002074 else:
2075 result[m.group(2)] = m.group(1)
2076 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002077 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002078 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002079 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002080 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002081
2082
Dan Albert8e0178d2015-01-27 15:53:15 -08002083def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2084 compress_type=None):
2085 import datetime
2086
2087 # http://b/18015246
2088 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2089 # for files larger than 2GiB. We can work around this by adjusting their
2090 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2091 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2092 # it isn't clear to me exactly what circumstances cause this).
2093 # `zipfile.write()` must be used directly to work around this.
2094 #
2095 # This mess can be avoided if we port to python3.
2096 saved_zip64_limit = zipfile.ZIP64_LIMIT
2097 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2098
2099 if compress_type is None:
2100 compress_type = zip_file.compression
2101 if arcname is None:
2102 arcname = filename
2103
2104 saved_stat = os.stat(filename)
2105
2106 try:
2107 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2108 # file to be zipped and reset it when we're done.
2109 os.chmod(filename, perms)
2110
2111 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002112 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2113 # intentional. zip stores datetimes in local time without a time zone
2114 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2115 # in the zip archive.
2116 local_epoch = datetime.datetime.fromtimestamp(0)
2117 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002118 os.utime(filename, (timestamp, timestamp))
2119
2120 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2121 finally:
2122 os.chmod(filename, saved_stat.st_mode)
2123 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2124 zipfile.ZIP64_LIMIT = saved_zip64_limit
2125
2126
Tao Bao58c1b962015-05-20 09:32:18 -07002127def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002128 compress_type=None):
2129 """Wrap zipfile.writestr() function to work around the zip64 limit.
2130
2131 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2132 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2133 when calling crc32(bytes).
2134
2135 But it still works fine to write a shorter string into a large zip file.
2136 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2137 when we know the string won't be too long.
2138 """
2139
2140 saved_zip64_limit = zipfile.ZIP64_LIMIT
2141 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2142
2143 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2144 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002145 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002146 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002147 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002148 else:
Tao Baof3282b42015-04-01 11:21:55 -07002149 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002150 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2151 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2152 # such a case (since
2153 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2154 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2155 # permission bits. We follow the logic in Python 3 to get consistent
2156 # behavior between using the two versions.
2157 if not zinfo.external_attr:
2158 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002159
2160 # If compress_type is given, it overrides the value in zinfo.
2161 if compress_type is not None:
2162 zinfo.compress_type = compress_type
2163
Tao Bao58c1b962015-05-20 09:32:18 -07002164 # If perms is given, it has a priority.
2165 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002166 # If perms doesn't set the file type, mark it as a regular file.
2167 if perms & 0o770000 == 0:
2168 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002169 zinfo.external_attr = perms << 16
2170
Tao Baof3282b42015-04-01 11:21:55 -07002171 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002172 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2173
Dan Albert8b72aef2015-03-23 19:13:21 -07002174 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002175 zipfile.ZIP64_LIMIT = saved_zip64_limit
2176
2177
Tao Bao89d7ab22017-12-14 17:05:33 -08002178def ZipDelete(zip_filename, entries):
2179 """Deletes entries from a ZIP file.
2180
2181 Since deleting entries from a ZIP file is not supported, it shells out to
2182 'zip -d'.
2183
2184 Args:
2185 zip_filename: The name of the ZIP file.
2186 entries: The name of the entry, or the list of names to be deleted.
2187
2188 Raises:
2189 AssertionError: In case of non-zero return from 'zip'.
2190 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002191 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002192 entries = [entries]
2193 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002194 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002195
2196
Tao Baof3282b42015-04-01 11:21:55 -07002197def ZipClose(zip_file):
2198 # http://b/18015246
2199 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2200 # central directory.
2201 saved_zip64_limit = zipfile.ZIP64_LIMIT
2202 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2203
2204 zip_file.close()
2205
2206 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002207
2208
2209class DeviceSpecificParams(object):
2210 module = None
2211 def __init__(self, **kwargs):
2212 """Keyword arguments to the constructor become attributes of this
2213 object, which is passed to all functions in the device-specific
2214 module."""
Tao Bao38884282019-07-10 22:20:56 -07002215 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002216 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002217 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002218
2219 if self.module is None:
2220 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002221 if not path:
2222 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002223 try:
2224 if os.path.isdir(path):
2225 info = imp.find_module("releasetools", [path])
2226 else:
2227 d, f = os.path.split(path)
2228 b, x = os.path.splitext(f)
2229 if x == ".py":
2230 f = b
2231 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002232 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002233 self.module = imp.load_module("device_specific", *info)
2234 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002235 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002236
2237 def _DoCall(self, function_name, *args, **kwargs):
2238 """Call the named function in the device-specific module, passing
2239 the given args and kwargs. The first argument to the call will be
2240 the DeviceSpecific object itself. If there is no module, or the
2241 module does not define the function, return the value of the
2242 'default' kwarg (which itself defaults to None)."""
2243 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002244 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002245 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2246
2247 def FullOTA_Assertions(self):
2248 """Called after emitting the block of assertions at the top of a
2249 full OTA package. Implementations can add whatever additional
2250 assertions they like."""
2251 return self._DoCall("FullOTA_Assertions")
2252
Doug Zongkere5ff5902012-01-17 10:55:37 -08002253 def FullOTA_InstallBegin(self):
2254 """Called at the start of full OTA installation."""
2255 return self._DoCall("FullOTA_InstallBegin")
2256
Yifan Hong10c530d2018-12-27 17:34:18 -08002257 def FullOTA_GetBlockDifferences(self):
2258 """Called during full OTA installation and verification.
2259 Implementation should return a list of BlockDifference objects describing
2260 the update on each additional partitions.
2261 """
2262 return self._DoCall("FullOTA_GetBlockDifferences")
2263
Doug Zongker05d3dea2009-06-22 11:32:31 -07002264 def FullOTA_InstallEnd(self):
2265 """Called at the end of full OTA installation; typically this is
2266 used to install the image for the device's baseband processor."""
2267 return self._DoCall("FullOTA_InstallEnd")
2268
2269 def IncrementalOTA_Assertions(self):
2270 """Called after emitting the block of assertions at the top of an
2271 incremental OTA package. Implementations can add whatever
2272 additional assertions they like."""
2273 return self._DoCall("IncrementalOTA_Assertions")
2274
Doug Zongkere5ff5902012-01-17 10:55:37 -08002275 def IncrementalOTA_VerifyBegin(self):
2276 """Called at the start of the verification phase of incremental
2277 OTA installation; additional checks can be placed here to abort
2278 the script before any changes are made."""
2279 return self._DoCall("IncrementalOTA_VerifyBegin")
2280
Doug Zongker05d3dea2009-06-22 11:32:31 -07002281 def IncrementalOTA_VerifyEnd(self):
2282 """Called at the end of the verification phase of incremental OTA
2283 installation; additional checks can be placed here to abort the
2284 script before any changes are made."""
2285 return self._DoCall("IncrementalOTA_VerifyEnd")
2286
Doug Zongkere5ff5902012-01-17 10:55:37 -08002287 def IncrementalOTA_InstallBegin(self):
2288 """Called at the start of incremental OTA installation (after
2289 verification is complete)."""
2290 return self._DoCall("IncrementalOTA_InstallBegin")
2291
Yifan Hong10c530d2018-12-27 17:34:18 -08002292 def IncrementalOTA_GetBlockDifferences(self):
2293 """Called during incremental OTA installation and verification.
2294 Implementation should return a list of BlockDifference objects describing
2295 the update on each additional partitions.
2296 """
2297 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2298
Doug Zongker05d3dea2009-06-22 11:32:31 -07002299 def IncrementalOTA_InstallEnd(self):
2300 """Called at the end of incremental OTA installation; typically
2301 this is used to install the image for the device's baseband
2302 processor."""
2303 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002304
Tao Bao9bc6bb22015-11-09 16:58:28 -08002305 def VerifyOTA_Assertions(self):
2306 return self._DoCall("VerifyOTA_Assertions")
2307
Tao Bao76def242017-11-21 09:25:31 -08002308
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002309class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002310 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002311 self.name = name
2312 self.data = data
2313 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002314 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002315 self.sha1 = sha1(data).hexdigest()
2316
2317 @classmethod
2318 def FromLocalFile(cls, name, diskname):
2319 f = open(diskname, "rb")
2320 data = f.read()
2321 f.close()
2322 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002323
2324 def WriteToTemp(self):
2325 t = tempfile.NamedTemporaryFile()
2326 t.write(self.data)
2327 t.flush()
2328 return t
2329
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002330 def WriteToDir(self, d):
2331 with open(os.path.join(d, self.name), "wb") as fp:
2332 fp.write(self.data)
2333
Geremy Condra36bd3652014-02-06 19:45:10 -08002334 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002335 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002336
Tao Bao76def242017-11-21 09:25:31 -08002337
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002338DIFF_PROGRAM_BY_EXT = {
2339 ".gz" : "imgdiff",
2340 ".zip" : ["imgdiff", "-z"],
2341 ".jar" : ["imgdiff", "-z"],
2342 ".apk" : ["imgdiff", "-z"],
2343 ".img" : "imgdiff",
2344 }
2345
Tao Bao76def242017-11-21 09:25:31 -08002346
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002347class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002348 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002349 self.tf = tf
2350 self.sf = sf
2351 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002352 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002353
2354 def ComputePatch(self):
2355 """Compute the patch (as a string of data) needed to turn sf into
2356 tf. Returns the same tuple as GetPatch()."""
2357
2358 tf = self.tf
2359 sf = self.sf
2360
Doug Zongker24cd2802012-08-14 16:36:15 -07002361 if self.diff_program:
2362 diff_program = self.diff_program
2363 else:
2364 ext = os.path.splitext(tf.name)[1]
2365 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002366
2367 ttemp = tf.WriteToTemp()
2368 stemp = sf.WriteToTemp()
2369
2370 ext = os.path.splitext(tf.name)[1]
2371
2372 try:
2373 ptemp = tempfile.NamedTemporaryFile()
2374 if isinstance(diff_program, list):
2375 cmd = copy.copy(diff_program)
2376 else:
2377 cmd = [diff_program]
2378 cmd.append(stemp.name)
2379 cmd.append(ttemp.name)
2380 cmd.append(ptemp.name)
2381 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002382 err = []
2383 def run():
2384 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002385 if e:
2386 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002387 th = threading.Thread(target=run)
2388 th.start()
2389 th.join(timeout=300) # 5 mins
2390 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002391 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002392 p.terminate()
2393 th.join(5)
2394 if th.is_alive():
2395 p.kill()
2396 th.join()
2397
Tianjie Xua2a9f992018-01-05 15:15:54 -08002398 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002399 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002400 self.patch = None
2401 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002402 diff = ptemp.read()
2403 finally:
2404 ptemp.close()
2405 stemp.close()
2406 ttemp.close()
2407
2408 self.patch = diff
2409 return self.tf, self.sf, self.patch
2410
2411
2412 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002413 """Returns a tuple of (target_file, source_file, patch_data).
2414
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002415 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002416 computing the patch failed.
2417 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002418 return self.tf, self.sf, self.patch
2419
2420
2421def ComputeDifferences(diffs):
2422 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002423 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002424
2425 # Do the largest files first, to try and reduce the long-pole effect.
2426 by_size = [(i.tf.size, i) for i in diffs]
2427 by_size.sort(reverse=True)
2428 by_size = [i[1] for i in by_size]
2429
2430 lock = threading.Lock()
2431 diff_iter = iter(by_size) # accessed under lock
2432
2433 def worker():
2434 try:
2435 lock.acquire()
2436 for d in diff_iter:
2437 lock.release()
2438 start = time.time()
2439 d.ComputePatch()
2440 dur = time.time() - start
2441 lock.acquire()
2442
2443 tf, sf, patch = d.GetPatch()
2444 if sf.name == tf.name:
2445 name = tf.name
2446 else:
2447 name = "%s (%s)" % (tf.name, sf.name)
2448 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002449 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002450 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002451 logger.info(
2452 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2453 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002454 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002455 except Exception:
2456 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002457 raise
2458
2459 # start worker threads; wait for them all to finish.
2460 threads = [threading.Thread(target=worker)
2461 for i in range(OPTIONS.worker_threads)]
2462 for th in threads:
2463 th.start()
2464 while threads:
2465 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002466
2467
Dan Albert8b72aef2015-03-23 19:13:21 -07002468class BlockDifference(object):
2469 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002470 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002471 self.tgt = tgt
2472 self.src = src
2473 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002474 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002475 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002476
Tao Baodd2a5892015-03-12 12:32:37 -07002477 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002478 version = max(
2479 int(i) for i in
2480 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002481 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002482 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002483
Tianjie Xu41976c72019-07-03 13:57:01 -07002484 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2485 version=self.version,
2486 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002487 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002488 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002489 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002490 self.touched_src_ranges = b.touched_src_ranges
2491 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002492
Yifan Hong10c530d2018-12-27 17:34:18 -08002493 # On devices with dynamic partitions, for new partitions,
2494 # src is None but OPTIONS.source_info_dict is not.
2495 if OPTIONS.source_info_dict is None:
2496 is_dynamic_build = OPTIONS.info_dict.get(
2497 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002498 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002499 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002500 is_dynamic_build = OPTIONS.source_info_dict.get(
2501 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002502 is_dynamic_source = partition in shlex.split(
2503 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002504
Yifan Hongbb2658d2019-01-25 12:30:58 -08002505 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002506 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2507
Yifan Hongbb2658d2019-01-25 12:30:58 -08002508 # For dynamic partitions builds, check partition list in both source
2509 # and target build because new partitions may be added, and existing
2510 # partitions may be removed.
2511 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2512
Yifan Hong10c530d2018-12-27 17:34:18 -08002513 if is_dynamic:
2514 self.device = 'map_partition("%s")' % partition
2515 else:
2516 if OPTIONS.source_info_dict is None:
2517 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2518 else:
2519 _, device_path = GetTypeAndDevice("/" + partition,
2520 OPTIONS.source_info_dict)
2521 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002522
Tao Baod8d14be2016-02-04 14:26:02 -08002523 @property
2524 def required_cache(self):
2525 return self._required_cache
2526
Tao Bao76def242017-11-21 09:25:31 -08002527 def WriteScript(self, script, output_zip, progress=None,
2528 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002529 if not self.src:
2530 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002531 script.Print("Patching %s image unconditionally..." % (self.partition,))
2532 else:
2533 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002534
Dan Albert8b72aef2015-03-23 19:13:21 -07002535 if progress:
2536 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002537 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002538
2539 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002540 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002541
Tao Bao9bc6bb22015-11-09 16:58:28 -08002542 def WriteStrictVerifyScript(self, script):
2543 """Verify all the blocks in the care_map, including clobbered blocks.
2544
2545 This differs from the WriteVerifyScript() function: a) it prints different
2546 error messages; b) it doesn't allow half-way updated images to pass the
2547 verification."""
2548
2549 partition = self.partition
2550 script.Print("Verifying %s..." % (partition,))
2551 ranges = self.tgt.care_map
2552 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002553 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002554 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2555 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002556 self.device, ranges_str,
2557 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002558 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002559 script.AppendExtra("")
2560
Tao Baod522bdc2016-04-12 15:53:16 -07002561 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002562 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002563
2564 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002565 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002566 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002567
2568 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002569 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002570 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002571 ranges = self.touched_src_ranges
2572 expected_sha1 = self.touched_src_sha1
2573 else:
2574 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2575 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002576
2577 # No blocks to be checked, skipping.
2578 if not ranges:
2579 return
2580
Tao Bao5ece99d2015-05-12 11:42:31 -07002581 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002582 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002583 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002584 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2585 '"%s.patch.dat")) then' % (
2586 self.device, ranges_str, expected_sha1,
2587 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002588 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002589 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002590
Tianjie Xufc3422a2015-12-15 11:53:59 -08002591 if self.version >= 4:
2592
2593 # Bug: 21124327
2594 # When generating incrementals for the system and vendor partitions in
2595 # version 4 or newer, explicitly check the first block (which contains
2596 # the superblock) of the partition to see if it's what we expect. If
2597 # this check fails, give an explicit log message about the partition
2598 # having been remounted R/W (the most likely explanation).
2599 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002600 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002601
2602 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002603 if partition == "system":
2604 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2605 else:
2606 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002607 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002608 'ifelse (block_image_recover({device}, "{ranges}") && '
2609 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002610 'package_extract_file("{partition}.transfer.list"), '
2611 '"{partition}.new.dat", "{partition}.patch.dat"), '
2612 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002613 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002614 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002615 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002616
Tao Baodd2a5892015-03-12 12:32:37 -07002617 # Abort the OTA update. Note that the incremental OTA cannot be applied
2618 # even if it may match the checksum of the target partition.
2619 # a) If version < 3, operations like move and erase will make changes
2620 # unconditionally and damage the partition.
2621 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002622 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002623 if partition == "system":
2624 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2625 else:
2626 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2627 script.AppendExtra((
2628 'abort("E%d: %s partition has unexpected contents");\n'
2629 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002630
Yifan Hong10c530d2018-12-27 17:34:18 -08002631 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002632 partition = self.partition
2633 script.Print('Verifying the updated %s image...' % (partition,))
2634 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2635 ranges = self.tgt.care_map
2636 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002637 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002638 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002639 self.device, ranges_str,
2640 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002641
2642 # Bug: 20881595
2643 # Verify that extended blocks are really zeroed out.
2644 if self.tgt.extended:
2645 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002646 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002647 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002648 self.device, ranges_str,
2649 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002650 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002651 if partition == "system":
2652 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2653 else:
2654 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002655 script.AppendExtra(
2656 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002657 ' abort("E%d: %s partition has unexpected non-zero contents after '
2658 'OTA update");\n'
2659 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002660 else:
2661 script.Print('Verified the updated %s image.' % (partition,))
2662
Tianjie Xu209db462016-05-24 17:34:52 -07002663 if partition == "system":
2664 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2665 else:
2666 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2667
Tao Bao5fcaaef2015-06-01 13:40:49 -07002668 script.AppendExtra(
2669 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002670 ' abort("E%d: %s partition has unexpected contents after OTA '
2671 'update");\n'
2672 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002673
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002674 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002675 ZipWrite(output_zip,
2676 '{}.transfer.list'.format(self.path),
2677 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002678
Tao Bao76def242017-11-21 09:25:31 -08002679 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2680 # its size. Quailty 9 almost triples the compression time but doesn't
2681 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002682 # zip | brotli(quality 6) | brotli(quality 9)
2683 # compressed_size: 942M | 869M (~8% reduced) | 854M
2684 # compression_time: 75s | 265s | 719s
2685 # decompression_time: 15s | 25s | 25s
2686
2687 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002688 brotli_cmd = ['brotli', '--quality=6',
2689 '--output={}.new.dat.br'.format(self.path),
2690 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002691 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002692 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002693
2694 new_data_name = '{}.new.dat.br'.format(self.partition)
2695 ZipWrite(output_zip,
2696 '{}.new.dat.br'.format(self.path),
2697 new_data_name,
2698 compress_type=zipfile.ZIP_STORED)
2699 else:
2700 new_data_name = '{}.new.dat'.format(self.partition)
2701 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2702
Dan Albert8e0178d2015-01-27 15:53:15 -08002703 ZipWrite(output_zip,
2704 '{}.patch.dat'.format(self.path),
2705 '{}.patch.dat'.format(self.partition),
2706 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002707
Tianjie Xu209db462016-05-24 17:34:52 -07002708 if self.partition == "system":
2709 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2710 else:
2711 code = ErrorCode.VENDOR_UPDATE_FAILURE
2712
Yifan Hong10c530d2018-12-27 17:34:18 -08002713 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002714 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002715 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002716 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002717 device=self.device, partition=self.partition,
2718 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002719 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002720
Dan Albert8b72aef2015-03-23 19:13:21 -07002721 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002722 data = source.ReadRangeSet(ranges)
2723 ctx = sha1()
2724
2725 for p in data:
2726 ctx.update(p)
2727
2728 return ctx.hexdigest()
2729
Tao Baoe9b61912015-07-09 17:37:49 -07002730 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2731 """Return the hash value for all zero blocks."""
2732 zero_block = '\x00' * 4096
2733 ctx = sha1()
2734 for _ in range(num_blocks):
2735 ctx.update(zero_block)
2736
2737 return ctx.hexdigest()
2738
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002739
Tianjie Xu41976c72019-07-03 13:57:01 -07002740# Expose these two classes to support vendor-specific scripts
2741DataImage = images.DataImage
2742EmptyImage = images.EmptyImage
2743
Tao Bao76def242017-11-21 09:25:31 -08002744
Doug Zongker96a57e72010-09-26 14:57:41 -07002745# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002746PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002747 "ext4": "EMMC",
2748 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002749 "f2fs": "EMMC",
2750 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002751}
Doug Zongker96a57e72010-09-26 14:57:41 -07002752
Tao Bao76def242017-11-21 09:25:31 -08002753
Doug Zongker96a57e72010-09-26 14:57:41 -07002754def GetTypeAndDevice(mount_point, info):
2755 fstab = info["fstab"]
2756 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002757 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2758 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002759 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002760 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002761
2762
2763def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002764 """Parses and converts a PEM-encoded certificate into DER-encoded.
2765
2766 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2767
2768 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002769 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002770 """
2771 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002772 save = False
2773 for line in data.split("\n"):
2774 if "--END CERTIFICATE--" in line:
2775 break
2776 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002777 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002778 if "--BEGIN CERTIFICATE--" in line:
2779 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002780 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002781 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002782
Tao Bao04e1f012018-02-04 12:13:35 -08002783
2784def ExtractPublicKey(cert):
2785 """Extracts the public key (PEM-encoded) from the given certificate file.
2786
2787 Args:
2788 cert: The certificate filename.
2789
2790 Returns:
2791 The public key string.
2792
2793 Raises:
2794 AssertionError: On non-zero return from 'openssl'.
2795 """
2796 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2797 # While openssl 1.1 writes the key into the given filename followed by '-out',
2798 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2799 # stdout instead.
2800 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2801 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2802 pubkey, stderrdata = proc.communicate()
2803 assert proc.returncode == 0, \
2804 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2805 return pubkey
2806
2807
Tao Bao1ac886e2019-06-26 11:58:22 -07002808def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002809 """Extracts the AVB public key from the given public or private key.
2810
2811 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002812 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002813 key: The input key file, which should be PEM-encoded public or private key.
2814
2815 Returns:
2816 The path to the extracted AVB public key file.
2817 """
2818 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2819 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002820 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002821 return output
2822
2823
Doug Zongker412c02f2014-02-13 10:58:24 -08002824def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2825 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002826 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002827
Tao Bao6d5d6232018-03-09 17:04:42 -08002828 Most of the space in the boot and recovery images is just the kernel, which is
2829 identical for the two, so the resulting patch should be efficient. Add it to
2830 the output zip, along with a shell script that is run from init.rc on first
2831 boot to actually do the patching and install the new recovery image.
2832
2833 Args:
2834 input_dir: The top-level input directory of the target-files.zip.
2835 output_sink: The callback function that writes the result.
2836 recovery_img: File object for the recovery image.
2837 boot_img: File objects for the boot image.
2838 info_dict: A dict returned by common.LoadInfoDict() on the input
2839 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002840 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002841 if info_dict is None:
2842 info_dict = OPTIONS.info_dict
2843
Tao Bao6d5d6232018-03-09 17:04:42 -08002844 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002845 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2846
2847 if board_uses_vendorimage:
2848 # In this case, the output sink is rooted at VENDOR
2849 recovery_img_path = "etc/recovery.img"
2850 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2851 sh_dir = "bin"
2852 else:
2853 # In this case the output sink is rooted at SYSTEM
2854 recovery_img_path = "vendor/etc/recovery.img"
2855 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2856 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002857
Tao Baof2cffbd2015-07-22 12:33:18 -07002858 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002859 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002860
2861 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002862 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002863 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002864 # With system-root-image, boot and recovery images will have mismatching
2865 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2866 # to handle such a case.
2867 if system_root_image:
2868 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002869 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002870 assert not os.path.exists(path)
2871 else:
2872 diff_program = ["imgdiff"]
2873 if os.path.exists(path):
2874 diff_program.append("-b")
2875 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002876 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002877 else:
2878 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002879
2880 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2881 _, _, patch = d.ComputePatch()
2882 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002883
Dan Albertebb19aa2015-03-27 19:11:53 -07002884 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002885 # The following GetTypeAndDevice()s need to use the path in the target
2886 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002887 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2888 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2889 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002890 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002891
Tao Baof2cffbd2015-07-22 12:33:18 -07002892 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002893
2894 # Note that we use /vendor to refer to the recovery resources. This will
2895 # work for a separate vendor partition mounted at /vendor or a
2896 # /system/vendor subdirectory on the system partition, for which init will
2897 # create a symlink from /vendor to /system/vendor.
2898
2899 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002900if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2901 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002902 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002903 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2904 log -t recovery "Installing new recovery image: succeeded" || \\
2905 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002906else
2907 log -t recovery "Recovery image already installed"
2908fi
2909""" % {'type': recovery_type,
2910 'device': recovery_device,
2911 'sha1': recovery_img.sha1,
2912 'size': recovery_img.size}
2913 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002914 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002915if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2916 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002917 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002918 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2919 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2920 log -t recovery "Installing new recovery image: succeeded" || \\
2921 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002922else
2923 log -t recovery "Recovery image already installed"
2924fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002925""" % {'boot_size': boot_img.size,
2926 'boot_sha1': boot_img.sha1,
2927 'recovery_size': recovery_img.size,
2928 'recovery_sha1': recovery_img.sha1,
2929 'boot_type': boot_type,
2930 'boot_device': boot_device,
2931 'recovery_type': recovery_type,
2932 'recovery_device': recovery_device,
2933 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002934
Bill Peckhame868aec2019-09-17 17:06:47 -07002935 # The install script location moved from /system/etc to /system/bin in the L
2936 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2937 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002938
Tao Bao32fcdab2018-10-12 10:30:39 -07002939 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002940
Tao Baoda30cfa2017-12-01 16:19:46 -08002941 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002942
2943
2944class DynamicPartitionUpdate(object):
2945 def __init__(self, src_group=None, tgt_group=None, progress=None,
2946 block_difference=None):
2947 self.src_group = src_group
2948 self.tgt_group = tgt_group
2949 self.progress = progress
2950 self.block_difference = block_difference
2951
2952 @property
2953 def src_size(self):
2954 if not self.block_difference:
2955 return 0
2956 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2957
2958 @property
2959 def tgt_size(self):
2960 if not self.block_difference:
2961 return 0
2962 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2963
2964 @staticmethod
2965 def _GetSparseImageSize(img):
2966 if not img:
2967 return 0
2968 return img.blocksize * img.total_blocks
2969
2970
2971class DynamicGroupUpdate(object):
2972 def __init__(self, src_size=None, tgt_size=None):
2973 # None: group does not exist. 0: no size limits.
2974 self.src_size = src_size
2975 self.tgt_size = tgt_size
2976
2977
2978class DynamicPartitionsDifference(object):
2979 def __init__(self, info_dict, block_diffs, progress_dict=None,
2980 source_info_dict=None):
2981 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002982 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002983
2984 self._remove_all_before_apply = False
2985 if source_info_dict is None:
2986 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002987 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002988
Tao Baof1113e92019-06-18 12:10:14 -07002989 block_diff_dict = collections.OrderedDict(
2990 [(e.partition, e) for e in block_diffs])
2991
Yifan Hong10c530d2018-12-27 17:34:18 -08002992 assert len(block_diff_dict) == len(block_diffs), \
2993 "Duplicated BlockDifference object for {}".format(
2994 [partition for partition, count in
2995 collections.Counter(e.partition for e in block_diffs).items()
2996 if count > 1])
2997
Yifan Hong79997e52019-01-23 16:56:19 -08002998 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002999
3000 for p, block_diff in block_diff_dict.items():
3001 self._partition_updates[p] = DynamicPartitionUpdate()
3002 self._partition_updates[p].block_difference = block_diff
3003
3004 for p, progress in progress_dict.items():
3005 if p in self._partition_updates:
3006 self._partition_updates[p].progress = progress
3007
3008 tgt_groups = shlex.split(info_dict.get(
3009 "super_partition_groups", "").strip())
3010 src_groups = shlex.split(source_info_dict.get(
3011 "super_partition_groups", "").strip())
3012
3013 for g in tgt_groups:
3014 for p in shlex.split(info_dict.get(
3015 "super_%s_partition_list" % g, "").strip()):
3016 assert p in self._partition_updates, \
3017 "{} is in target super_{}_partition_list but no BlockDifference " \
3018 "object is provided.".format(p, g)
3019 self._partition_updates[p].tgt_group = g
3020
3021 for g in src_groups:
3022 for p in shlex.split(source_info_dict.get(
3023 "super_%s_partition_list" % g, "").strip()):
3024 assert p in self._partition_updates, \
3025 "{} is in source super_{}_partition_list but no BlockDifference " \
3026 "object is provided.".format(p, g)
3027 self._partition_updates[p].src_group = g
3028
Yifan Hong45433e42019-01-18 13:55:25 -08003029 target_dynamic_partitions = set(shlex.split(info_dict.get(
3030 "dynamic_partition_list", "").strip()))
3031 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3032 if u.tgt_size)
3033 assert block_diffs_with_target == target_dynamic_partitions, \
3034 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3035 list(target_dynamic_partitions), list(block_diffs_with_target))
3036
3037 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3038 "dynamic_partition_list", "").strip()))
3039 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3040 if u.src_size)
3041 assert block_diffs_with_source == source_dynamic_partitions, \
3042 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3043 list(source_dynamic_partitions), list(block_diffs_with_source))
3044
Yifan Hong10c530d2018-12-27 17:34:18 -08003045 if self._partition_updates:
3046 logger.info("Updating dynamic partitions %s",
3047 self._partition_updates.keys())
3048
Yifan Hong79997e52019-01-23 16:56:19 -08003049 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003050
3051 for g in tgt_groups:
3052 self._group_updates[g] = DynamicGroupUpdate()
3053 self._group_updates[g].tgt_size = int(info_dict.get(
3054 "super_%s_group_size" % g, "0").strip())
3055
3056 for g in src_groups:
3057 if g not in self._group_updates:
3058 self._group_updates[g] = DynamicGroupUpdate()
3059 self._group_updates[g].src_size = int(source_info_dict.get(
3060 "super_%s_group_size" % g, "0").strip())
3061
3062 self._Compute()
3063
3064 def WriteScript(self, script, output_zip, write_verify_script=False):
3065 script.Comment('--- Start patching dynamic partitions ---')
3066 for p, u in self._partition_updates.items():
3067 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3068 script.Comment('Patch partition %s' % p)
3069 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3070 write_verify_script=False)
3071
3072 op_list_path = MakeTempFile()
3073 with open(op_list_path, 'w') as f:
3074 for line in self._op_list:
3075 f.write('{}\n'.format(line))
3076
3077 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3078
3079 script.Comment('Update dynamic partition metadata')
3080 script.AppendExtra('assert(update_dynamic_partitions('
3081 'package_extract_file("dynamic_partitions_op_list")));')
3082
3083 if write_verify_script:
3084 for p, u in self._partition_updates.items():
3085 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3086 u.block_difference.WritePostInstallVerifyScript(script)
3087 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3088
3089 for p, u in self._partition_updates.items():
3090 if u.tgt_size and u.src_size <= u.tgt_size:
3091 script.Comment('Patch partition %s' % p)
3092 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3093 write_verify_script=write_verify_script)
3094 if write_verify_script:
3095 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3096
3097 script.Comment('--- End patching dynamic partitions ---')
3098
3099 def _Compute(self):
3100 self._op_list = list()
3101
3102 def append(line):
3103 self._op_list.append(line)
3104
3105 def comment(line):
3106 self._op_list.append("# %s" % line)
3107
3108 if self._remove_all_before_apply:
3109 comment('Remove all existing dynamic partitions and groups before '
3110 'applying full OTA')
3111 append('remove_all_groups')
3112
3113 for p, u in self._partition_updates.items():
3114 if u.src_group and not u.tgt_group:
3115 append('remove %s' % p)
3116
3117 for p, u in self._partition_updates.items():
3118 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3119 comment('Move partition %s from %s to default' % (p, u.src_group))
3120 append('move %s default' % p)
3121
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 comment('Shrink partition %s from %d to %d' %
3125 (p, u.src_size, u.tgt_size))
3126 append('resize %s %s' % (p, u.tgt_size))
3127
3128 for g, u in self._group_updates.items():
3129 if u.src_size is not None and u.tgt_size is None:
3130 append('remove_group %s' % g)
3131 if (u.src_size is not None and u.tgt_size is not None and
3132 u.src_size > u.tgt_size):
3133 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3134 append('resize_group %s %d' % (g, u.tgt_size))
3135
3136 for g, u in self._group_updates.items():
3137 if u.src_size is None and u.tgt_size is not None:
3138 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3139 append('add_group %s %d' % (g, u.tgt_size))
3140 if (u.src_size is not None and u.tgt_size is not None and
3141 u.src_size < u.tgt_size):
3142 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3143 append('resize_group %s %d' % (g, u.tgt_size))
3144
3145 for p, u in self._partition_updates.items():
3146 if u.tgt_group and not u.src_group:
3147 comment('Add partition %s to group %s' % (p, u.tgt_group))
3148 append('add %s %s' % (p, u.tgt_group))
3149
3150 for p, u in self._partition_updates.items():
3151 if u.tgt_size and u.src_size < u.tgt_size:
3152 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3153 append('resize %s %d' % (p, u.tgt_size))
3154
3155 for p, u in self._partition_updates.items():
3156 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3157 comment('Move partition %s from default to %s' %
3158 (p, u.tgt_group))
3159 append('move %s %s' % (p, u.tgt_group))