blob: df57e3775d6153bf314f5893dd0570fced29a528 [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"
363 self._oem_props = info_dict.get("oem_fingerprint_properties")
364
365 if self._oem_props:
366 assert oem_dicts, "OEM source required for this build"
367
Daniel Normanab5acef2020-01-08 17:01:11 -0800368 def check_fingerprint(fingerprint):
369 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
370 raise ValueError(
371 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
372 "3.2.2. Build Parameters.".format(fingerprint))
373
374
375 self._partition_fingerprints = {}
376 for partition in PARTITIONS_WITH_CARE_MAP:
377 try:
378 fingerprint = self.CalculatePartitionFingerprint(partition)
379 check_fingerprint(fingerprint)
380 self._partition_fingerprints[partition] = fingerprint
381 except ExternalError:
382 continue
383 if "system" in self._partition_fingerprints:
384 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
385 # need a fingerprint when creating the image.
386 self._partition_fingerprints[
387 "system_other"] = self._partition_fingerprints["system"]
388
Tao Bao1c320f82019-10-04 23:25:12 -0700389 # These two should be computed only after setting self._oem_props.
390 self._device = self.GetOemProperty("ro.product.device")
391 self._fingerprint = self.CalculateFingerprint()
Daniel Normanab5acef2020-01-08 17:01:11 -0800392 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700393
394 @property
395 def is_ab(self):
396 return self._is_ab
397
398 @property
399 def device(self):
400 return self._device
401
402 @property
403 def fingerprint(self):
404 return self._fingerprint
405
406 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700407 def oem_props(self):
408 return self._oem_props
409
410 def __getitem__(self, key):
411 return self.info_dict[key]
412
413 def __setitem__(self, key, value):
414 self.info_dict[key] = value
415
416 def get(self, key, default=None):
417 return self.info_dict.get(key, default)
418
419 def items(self):
420 return self.info_dict.items()
421
Daniel Normanab5acef2020-01-08 17:01:11 -0800422 def GetPartitionBuildProp(self, prop, partition):
423 """Returns the inquired build property for the provided partition."""
424 # If provided a partition for this property, only look within that
425 # partition's build.prop.
426 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
427 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
428 else:
429 prop = prop.replace("ro.", "ro.{}.".format(partition))
430 try:
431 return self.info_dict.get("{}.build.prop".format(partition), {})[prop]
432 except KeyError:
433 raise ExternalError("couldn't find %s in %s.build.prop" %
434 (prop, partition))
435
Tao Bao1c320f82019-10-04 23:25:12 -0700436 def GetBuildProp(self, prop):
Daniel Normanab5acef2020-01-08 17:01:11 -0800437 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700438 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
439 return self._ResolveRoProductBuildProp(prop)
440
441 try:
442 return self.info_dict.get("build.prop", {})[prop]
443 except KeyError:
444 raise ExternalError("couldn't find %s in build.prop" % (prop,))
445
446 def _ResolveRoProductBuildProp(self, prop):
447 """Resolves the inquired ro.product.* build property"""
448 prop_val = self.info_dict.get("build.prop", {}).get(prop)
449 if prop_val:
450 return prop_val
451
Steven Laverdd33d752020-04-27 16:26:31 -0700452 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tao Bao1c320f82019-10-04 23:25:12 -0700453 source_order_val = self.info_dict.get("build.prop", {}).get(
454 "ro.product.property_source_order")
455 if source_order_val:
456 source_order = source_order_val.split(",")
457 else:
Steven Laverdd33d752020-04-27 16:26:31 -0700458 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700459
460 # Check that all sources in ro.product.property_source_order are valid
Steven Laverdd33d752020-04-27 16:26:31 -0700461 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700462 raise ExternalError(
463 "Invalid ro.product.property_source_order '{}'".format(source_order))
464
465 for source in source_order:
466 source_prop = prop.replace(
467 "ro.product", "ro.product.{}".format(source), 1)
468 prop_val = self.info_dict.get(
469 "{}.build.prop".format(source), {}).get(source_prop)
470 if prop_val:
471 return prop_val
472
473 raise ExternalError("couldn't resolve {}".format(prop))
474
Steven Laverdd33d752020-04-27 16:26:31 -0700475 def _GetRoProductPropsDefaultSourceOrder(self):
476 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
477 # values of these properties for each Android release.
478 android_codename = self.info_dict.get("build.prop", {}).get(
479 "ro.build.version.codename")
480 if android_codename == "REL":
481 android_version = self.info_dict.get("build.prop", {}).get(
482 "ro.build.version.release")
483 if android_version == "10":
484 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
485 # NOTE: float() conversion of android_version will have rounding error.
486 # We are checking for "9" or less, and using "< 10" is well outside of
487 # possible floating point rounding.
488 try:
489 android_version_val = float(android_version)
490 except ValueError:
491 android_version_val = 0
492 if android_version_val < 10:
493 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
494 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
495
Tao Bao1c320f82019-10-04 23:25:12 -0700496 def GetOemProperty(self, key):
497 if self.oem_props is not None and key in self.oem_props:
498 return self.oem_dicts[0][key]
499 return self.GetBuildProp(key)
500
Daniel Normanab5acef2020-01-08 17:01:11 -0800501 def GetPartitionFingerprint(self, partition):
502 return self._partition_fingerprints.get(partition, None)
503
504 def CalculatePartitionFingerprint(self, partition):
505 try:
506 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
507 except ExternalError:
508 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
509 self.GetPartitionBuildProp("ro.product.brand", partition),
510 self.GetPartitionBuildProp("ro.product.name", partition),
511 self.GetPartitionBuildProp("ro.product.device", partition),
512 self.GetPartitionBuildProp("ro.build.version.release", partition),
513 self.GetPartitionBuildProp("ro.build.id", partition),
514 self.GetPartitionBuildProp("ro.build.version.incremental", partition),
515 self.GetPartitionBuildProp("ro.build.type", partition),
516 self.GetPartitionBuildProp("ro.build.tags", partition))
517
Tao Bao1c320f82019-10-04 23:25:12 -0700518 def CalculateFingerprint(self):
519 if self.oem_props is None:
520 try:
521 return self.GetBuildProp("ro.build.fingerprint")
522 except ExternalError:
523 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
524 self.GetBuildProp("ro.product.brand"),
525 self.GetBuildProp("ro.product.name"),
526 self.GetBuildProp("ro.product.device"),
527 self.GetBuildProp("ro.build.version.release"),
528 self.GetBuildProp("ro.build.id"),
529 self.GetBuildProp("ro.build.version.incremental"),
530 self.GetBuildProp("ro.build.type"),
531 self.GetBuildProp("ro.build.tags"))
532 return "%s/%s/%s:%s" % (
533 self.GetOemProperty("ro.product.brand"),
534 self.GetOemProperty("ro.product.name"),
535 self.GetOemProperty("ro.product.device"),
536 self.GetBuildProp("ro.build.thumbprint"))
537
538 def WriteMountOemScript(self, script):
539 assert self.oem_props is not None
540 recovery_mount_options = self.info_dict.get("recovery_mount_options")
541 script.Mount("/oem", recovery_mount_options)
542
543 def WriteDeviceAssertions(self, script, oem_no_mount):
544 # Read the property directly if not using OEM properties.
545 if not self.oem_props:
546 script.AssertDevice(self.device)
547 return
548
549 # Otherwise assert OEM properties.
550 if not self.oem_dicts:
551 raise ExternalError(
552 "No OEM file provided to answer expected assertions")
553
554 for prop in self.oem_props.split():
555 values = []
556 for oem_dict in self.oem_dicts:
557 if prop in oem_dict:
558 values.append(oem_dict[prop])
559 if not values:
560 raise ExternalError(
561 "The OEM file is missing the property %s" % (prop,))
562 script.AssertOemProperty(prop, values, oem_no_mount)
563
564
Tao Bao410ad8b2018-08-24 12:08:38 -0700565def LoadInfoDict(input_file, repacking=False):
566 """Loads the key/value pairs from the given input target_files.
567
568 It reads `META/misc_info.txt` file in the target_files input, does sanity
569 checks and returns the parsed key/value pairs for to the given build. It's
570 usually called early when working on input target_files files, e.g. when
571 generating OTAs, or signing builds. Note that the function may be called
572 against an old target_files file (i.e. from past dessert releases). So the
573 property parsing needs to be backward compatible.
574
575 In a `META/misc_info.txt`, a few properties are stored as links to the files
576 in the PRODUCT_OUT directory. It works fine with the build system. However,
577 they are no longer available when (re)generating images from target_files zip.
578 When `repacking` is True, redirect these properties to the actual files in the
579 unzipped directory.
580
581 Args:
582 input_file: The input target_files file, which could be an open
583 zipfile.ZipFile instance, or a str for the dir that contains the files
584 unzipped from a target_files file.
585 repacking: Whether it's trying repack an target_files file after loading the
586 info dict (default: False). If so, it will rewrite a few loaded
587 properties (e.g. selinux_fc, root_dir) to point to the actual files in
588 target_files file. When doing repacking, `input_file` must be a dir.
589
590 Returns:
591 A dict that contains the parsed key/value pairs.
592
593 Raises:
594 AssertionError: On invalid input arguments.
595 ValueError: On malformed input values.
596 """
597 if repacking:
598 assert isinstance(input_file, str), \
599 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700600
Doug Zongkerc9253822014-02-04 12:17:58 -0800601 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700602 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800603 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800604 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700605 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800606 try:
607 with open(path) as f:
608 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700609 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800610 if e.errno == errno.ENOENT:
611 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800612
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700613 try:
Michael Runge6e836112014-04-15 17:40:21 -0700614 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700615 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700616 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700617
Tao Bao410ad8b2018-08-24 12:08:38 -0700618 if "recovery_api_version" not in d:
619 raise ValueError("Failed to find 'recovery_api_version'")
620 if "fstab_version" not in d:
621 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800622
Tao Bao410ad8b2018-08-24 12:08:38 -0700623 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700624 # "selinux_fc" properties should point to the file_contexts files
625 # (file_contexts.bin) under META/.
626 for key in d:
627 if key.endswith("selinux_fc"):
628 fc_basename = os.path.basename(d[key])
629 fc_config = os.path.join(input_file, "META", fc_basename)
630 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700631
Daniel Norman72c626f2019-05-13 15:58:14 -0700632 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700633
Tom Cherryd14b8952018-08-09 14:26:00 -0700634 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700635 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700636 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700637 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700638
David Anderson0ec64ac2019-12-06 12:21:18 -0800639 # Redirect {partition}_base_fs_file for each of the named partitions.
640 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
641 key_name = part_name + "_base_fs_file"
642 if key_name not in d:
643 continue
644 basename = os.path.basename(d[key_name])
645 base_fs_file = os.path.join(input_file, "META", basename)
646 if os.path.exists(base_fs_file):
647 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700648 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700649 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800650 "Failed to find %s base fs file: %s", part_name, base_fs_file)
651 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700652
Doug Zongker37974732010-09-16 17:44:38 -0700653 def makeint(key):
654 if key in d:
655 d[key] = int(d[key], 0)
656
657 makeint("recovery_api_version")
658 makeint("blocksize")
659 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700660 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700661 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700662 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700663 makeint("recovery_size")
664 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800665 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700666
Tao Bao765668f2019-10-04 22:03:00 -0700667 # Load recovery fstab if applicable.
668 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800669
Tianjie Xu861f4132018-09-12 11:49:33 -0700670 # Tries to load the build props for all partitions with care_map, including
671 # system and vendor.
672 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800673 partition_prop = "{}.build.prop".format(partition)
674 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700675 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800676 # Some partition might use /<partition>/etc/build.prop as the new path.
677 # TODO: try new path first when majority of them switch to the new path.
678 if not d[partition_prop]:
679 d[partition_prop] = LoadBuildProp(
680 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700681 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800682
Tao Bao3ed35d32019-10-07 20:48:48 -0700683 # Set up the salt (based on fingerprint) that will be used when adding AVB
684 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800685 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700686 build_info = BuildInfo(d)
Daniel Normanab5acef2020-01-08 17:01:11 -0800687 for partition in PARTITIONS_WITH_CARE_MAP:
688 fingerprint = build_info.GetPartitionFingerprint(partition)
689 if fingerprint:
690 d["avb_{}_salt".format(partition)] = sha256(fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800691
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700692 return d
693
Tao Baod1de6f32017-03-01 16:38:48 -0800694
Tao Baobcd1d162017-08-26 13:10:26 -0700695def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700696 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700697 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700698 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700699 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700700 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700701 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700702
Tao Baod1de6f32017-03-01 16:38:48 -0800703
Daniel Norman4cc9df62019-07-18 10:11:07 -0700704def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900705 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700706 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900707
Daniel Norman4cc9df62019-07-18 10:11:07 -0700708
709def LoadDictionaryFromFile(file_path):
710 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900711 return LoadDictionaryFromLines(lines)
712
713
Michael Runge6e836112014-04-15 17:40:21 -0700714def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700715 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700716 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700717 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700718 if not line or line.startswith("#"):
719 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700720 if "=" in line:
721 name, value = line.split("=", 1)
722 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700723 return d
724
Tao Baod1de6f32017-03-01 16:38:48 -0800725
Tianjie Xucfa86222016-03-07 16:31:19 -0800726def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
727 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700728 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800729 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700730 self.mount_point = mount_point
731 self.fs_type = fs_type
732 self.device = device
733 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700734 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700735
736 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800737 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700738 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700739 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700740 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700741
Tao Baod1de6f32017-03-01 16:38:48 -0800742 assert fstab_version == 2
743
744 d = {}
745 for line in data.split("\n"):
746 line = line.strip()
747 if not line or line.startswith("#"):
748 continue
749
750 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
751 pieces = line.split()
752 if len(pieces) != 5:
753 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
754
755 # Ignore entries that are managed by vold.
756 options = pieces[4]
757 if "voldmanaged=" in options:
758 continue
759
760 # It's a good line, parse it.
761 length = 0
762 options = options.split(",")
763 for i in options:
764 if i.startswith("length="):
765 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800766 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800767 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700768 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800769
Tao Baod1de6f32017-03-01 16:38:48 -0800770 mount_flags = pieces[3]
771 # Honor the SELinux context if present.
772 context = None
773 for i in mount_flags.split(","):
774 if i.startswith("context="):
775 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800776
Tao Baod1de6f32017-03-01 16:38:48 -0800777 mount_point = pieces[1]
778 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
779 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800780
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700781 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700782 # system. Other areas assume system is always at "/system" so point /system
783 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700784 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800785 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700786 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700787 return d
788
789
Tao Bao765668f2019-10-04 22:03:00 -0700790def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
791 """Finds the path to recovery fstab and loads its contents."""
792 # recovery fstab is only meaningful when installing an update via recovery
793 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
794 if info_dict.get('ab_update') == 'true':
795 return None
796
797 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
798 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
799 # cases, since it may load the info_dict from an old build (e.g. when
800 # generating incremental OTAs from that build).
801 system_root_image = info_dict.get('system_root_image') == 'true'
802 if info_dict.get('no_recovery') != 'true':
803 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
804 if isinstance(input_file, zipfile.ZipFile):
805 if recovery_fstab_path not in input_file.namelist():
806 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
807 else:
808 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
809 if not os.path.exists(path):
810 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
811 return LoadRecoveryFSTab(
812 read_helper, info_dict['fstab_version'], recovery_fstab_path,
813 system_root_image)
814
815 if info_dict.get('recovery_as_boot') == 'true':
816 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
817 if isinstance(input_file, zipfile.ZipFile):
818 if recovery_fstab_path not in input_file.namelist():
819 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
820 else:
821 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
822 if not os.path.exists(path):
823 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
824 return LoadRecoveryFSTab(
825 read_helper, info_dict['fstab_version'], recovery_fstab_path,
826 system_root_image)
827
828 return None
829
830
Doug Zongker37974732010-09-16 17:44:38 -0700831def DumpInfoDict(d):
832 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700833 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700834
Dan Albert8b72aef2015-03-23 19:13:21 -0700835
Daniel Norman55417142019-11-25 16:04:36 -0800836def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700837 """Merges dynamic partition info variables.
838
839 Args:
840 framework_dict: The dictionary of dynamic partition info variables from the
841 partial framework target files.
842 vendor_dict: The dictionary of dynamic partition info variables from the
843 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700844
845 Returns:
846 The merged dynamic partition info dictionary.
847 """
848 merged_dict = {}
849 # Partition groups and group sizes are defined by the vendor dict because
850 # these values may vary for each board that uses a shared system image.
851 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800852 framework_dynamic_partition_list = framework_dict.get(
853 "dynamic_partition_list", "")
854 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
855 merged_dict["dynamic_partition_list"] = ("%s %s" % (
856 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700857 for partition_group in merged_dict["super_partition_groups"].split(" "):
858 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800859 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700860 if key not in vendor_dict:
861 raise ValueError("Vendor dict does not contain required key %s." % key)
862 merged_dict[key] = vendor_dict[key]
863
864 # Set the partition group's partition list using a concatenation of the
865 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800866 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700867 merged_dict[key] = (
868 "%s %s" %
869 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530870
871 # Pick virtual ab related flags from vendor dict, if defined.
872 if "virtual_ab" in vendor_dict.keys():
873 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
874 if "virtual_ab_retrofit" in vendor_dict.keys():
875 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700876 return merged_dict
877
878
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800879def AppendAVBSigningArgs(cmd, partition):
880 """Append signing arguments for avbtool."""
881 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
882 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700883 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
884 new_key_path = os.path.join(OPTIONS.search_path, key_path)
885 if os.path.exists(new_key_path):
886 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800887 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
888 if key_path and algorithm:
889 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700890 avb_salt = OPTIONS.info_dict.get("avb_salt")
891 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700892 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700893 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800894
895
Tao Bao765668f2019-10-04 22:03:00 -0700896def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700897 """Returns the VBMeta arguments for partition.
898
899 It sets up the VBMeta argument by including the partition descriptor from the
900 given 'image', or by configuring the partition as a chained partition.
901
902 Args:
903 partition: The name of the partition (e.g. "system").
904 image: The path to the partition image.
905 info_dict: A dict returned by common.LoadInfoDict(). Will use
906 OPTIONS.info_dict if None has been given.
907
908 Returns:
909 A list of VBMeta arguments.
910 """
911 if info_dict is None:
912 info_dict = OPTIONS.info_dict
913
914 # Check if chain partition is used.
915 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +0800916 if not key_path:
917 return ["--include_descriptors_from_image", image]
918
919 # For a non-A/B device, we don't chain /recovery nor include its descriptor
920 # into vbmeta.img. The recovery image will be configured on an independent
921 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
922 # See details at
923 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -0700924 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +0800925 return []
926
927 # Otherwise chain the partition into vbmeta.
928 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
929 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -0700930
931
Tao Bao02a08592018-07-22 12:40:45 -0700932def GetAvbChainedPartitionArg(partition, info_dict, key=None):
933 """Constructs and returns the arg to build or verify a chained partition.
934
935 Args:
936 partition: The partition name.
937 info_dict: The info dict to look up the key info and rollback index
938 location.
939 key: The key to be used for building or verifying the partition. Defaults to
940 the key listed in info_dict.
941
942 Returns:
943 A string of form "partition:rollback_index_location:key" that can be used to
944 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700945 """
946 if key is None:
947 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700948 if key and not os.path.exists(key) and OPTIONS.search_path:
949 new_key_path = os.path.join(OPTIONS.search_path, key)
950 if os.path.exists(new_key_path):
951 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700952 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700953 rollback_index_location = info_dict[
954 "avb_" + partition + "_rollback_index_location"]
955 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
956
957
Daniel Norman276f0622019-07-26 14:13:51 -0700958def BuildVBMeta(image_path, partitions, name, needed_partitions):
959 """Creates a VBMeta image.
960
961 It generates the requested VBMeta image. The requested image could be for
962 top-level or chained VBMeta image, which is determined based on the name.
963
964 Args:
965 image_path: The output path for the new VBMeta image.
966 partitions: A dict that's keyed by partition names with image paths as
967 values. Only valid partition names are accepted, as listed in
968 common.AVB_PARTITIONS.
969 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
970 needed_partitions: Partitions whose descriptors should be included into the
971 generated VBMeta image.
972
973 Raises:
974 AssertionError: On invalid input args.
975 """
976 avbtool = OPTIONS.info_dict["avb_avbtool"]
977 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
978 AppendAVBSigningArgs(cmd, name)
979
980 for partition, path in partitions.items():
981 if partition not in needed_partitions:
982 continue
983 assert (partition in AVB_PARTITIONS or
984 partition in AVB_VBMETA_PARTITIONS), \
985 'Unknown partition: {}'.format(partition)
986 assert os.path.exists(path), \
987 'Failed to find {} for {}'.format(path, partition)
988 cmd.extend(GetAvbPartitionArg(partition, path))
989
990 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
991 if args and args.strip():
992 split_args = shlex.split(args)
993 for index, arg in enumerate(split_args[:-1]):
994 # Sanity check that the image file exists. Some images might be defined
995 # as a path relative to source tree, which may not be available at the
996 # same location when running this script (we have the input target_files
997 # zip only). For such cases, we additionally scan other locations (e.g.
998 # IMAGES/, RADIO/, etc) before bailing out.
999 if arg == '--include_descriptors_from_image':
1000 image_path = split_args[index + 1]
1001 if os.path.exists(image_path):
1002 continue
1003 found = False
1004 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1005 alt_path = os.path.join(
1006 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
1007 if os.path.exists(alt_path):
1008 split_args[index + 1] = alt_path
1009 found = True
1010 break
1011 assert found, 'Failed to find {}'.format(image_path)
1012 cmd.extend(split_args)
1013
1014 RunAndCheckOutput(cmd)
1015
Dan Austin52903642019-12-12 15:44:00 -08001016 if OPTIONS.aftl_server is not None:
1017 # Ensure the other AFTL parameters are set as well.
1018 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1019 assert OPTIONS.aftl_manufacturer_key_path is not None, 'No AFTL manufacturer key provided.'
1020 assert OPTIONS.aftl_signer_helper is not None, 'No AFTL signer helper provided.'
1021 # AFTL inclusion proof generation code will go here.
Daniel Norman276f0622019-07-26 14:13:51 -07001022
Steve Mucklee1b10862019-07-10 10:49:37 -07001023def _MakeRamdisk(sourcedir, fs_config_file=None):
1024 ramdisk_img = tempfile.NamedTemporaryFile()
1025
1026 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1027 cmd = ["mkbootfs", "-f", fs_config_file,
1028 os.path.join(sourcedir, "RAMDISK")]
1029 else:
1030 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1031 p1 = Run(cmd, stdout=subprocess.PIPE)
1032 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1033
1034 p2.wait()
1035 p1.wait()
1036 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1037 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1038
1039 return ramdisk_img
1040
1041
Steve Mucklef83e3c32020-04-08 18:27:00 -07001042def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001043 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001044 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001045
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001046 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001047 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1048 we are building a two-step special image (i.e. building a recovery image to
1049 be loaded into /boot in two-step OTAs).
1050
1051 Return the image data, or None if sourcedir does not appear to contains files
1052 for building the requested image.
1053 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001054
Steve Mucklef83e3c32020-04-08 18:27:00 -07001055 # "boot" or "recovery", without extension.
1056 partition_name = os.path.basename(sourcedir).lower()
1057
1058 if partition_name == "recovery":
1059 kernel = "kernel"
1060 else:
1061 kernel = image_name.replace("boot", "kernel")
1062 kernel = kernel.replace(".img","")
1063 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001064 return None
1065
1066 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001067 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001068
Doug Zongkerd5131602012-08-02 14:46:42 -07001069 if info_dict is None:
1070 info_dict = OPTIONS.info_dict
1071
Doug Zongkereef39442009-04-02 12:14:19 -07001072 img = tempfile.NamedTemporaryFile()
1073
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001074 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001075 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001076
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001077 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1078 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1079
Steve Mucklef83e3c32020-04-08 18:27:00 -07001080 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001081
Benoit Fradina45a8682014-07-14 21:00:43 +02001082 fn = os.path.join(sourcedir, "second")
1083 if os.access(fn, os.F_OK):
1084 cmd.append("--second")
1085 cmd.append(fn)
1086
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001087 fn = os.path.join(sourcedir, "dtb")
1088 if os.access(fn, os.F_OK):
1089 cmd.append("--dtb")
1090 cmd.append(fn)
1091
Doug Zongker171f1cd2009-06-15 22:36:37 -07001092 fn = os.path.join(sourcedir, "cmdline")
1093 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001094 cmd.append("--cmdline")
1095 cmd.append(open(fn).read().rstrip("\n"))
1096
1097 fn = os.path.join(sourcedir, "base")
1098 if os.access(fn, os.F_OK):
1099 cmd.append("--base")
1100 cmd.append(open(fn).read().rstrip("\n"))
1101
Ying Wang4de6b5b2010-08-25 14:29:34 -07001102 fn = os.path.join(sourcedir, "pagesize")
1103 if os.access(fn, os.F_OK):
1104 cmd.append("--pagesize")
1105 cmd.append(open(fn).read().rstrip("\n"))
1106
Steve Muckle759d0c82020-03-16 19:13:46 -07001107 if partition_name == "recovery":
1108 args = info_dict.get("recovery_mkbootimg_args")
1109 else:
1110 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001111 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001112 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001113
Tao Bao76def242017-11-21 09:25:31 -08001114 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001115 if args and args.strip():
1116 cmd.extend(shlex.split(args))
1117
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001118 if has_ramdisk:
1119 cmd.extend(["--ramdisk", ramdisk_img.name])
1120
Tao Baod95e9fd2015-03-29 23:07:41 -07001121 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001122 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001123 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001124 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001125 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001126 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001127
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001128 if partition_name == "recovery":
1129 if info_dict.get("include_recovery_dtbo") == "true":
1130 fn = os.path.join(sourcedir, "recovery_dtbo")
1131 cmd.extend(["--recovery_dtbo", fn])
1132 if info_dict.get("include_recovery_acpio") == "true":
1133 fn = os.path.join(sourcedir, "recovery_acpio")
1134 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001135
Tao Bao986ee862018-10-04 15:46:16 -07001136 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001137
Tao Bao76def242017-11-21 09:25:31 -08001138 if (info_dict.get("boot_signer") == "true" and
1139 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001140 # Hard-code the path as "/boot" for two-step special recovery image (which
1141 # will be loaded into /boot during the two-step OTA).
1142 if two_step_image:
1143 path = "/boot"
1144 else:
Tao Baobf70c312017-07-11 17:27:55 -07001145 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001146 cmd = [OPTIONS.boot_signer_path]
1147 cmd.extend(OPTIONS.boot_signer_args)
1148 cmd.extend([path, img.name,
1149 info_dict["verity_key"] + ".pk8",
1150 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001151 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001152
Tao Baod95e9fd2015-03-29 23:07:41 -07001153 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001154 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001155 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001156 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001157 # We have switched from the prebuilt futility binary to using the tool
1158 # (futility-host) built from the source. Override the setting in the old
1159 # TF.zip.
1160 futility = info_dict["futility"]
1161 if futility.startswith("prebuilts/"):
1162 futility = "futility-host"
1163 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001164 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001165 info_dict["vboot_key"] + ".vbprivk",
1166 info_dict["vboot_subkey"] + ".vbprivk",
1167 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001168 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001169 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001170
Tao Baof3282b42015-04-01 11:21:55 -07001171 # Clean up the temp files.
1172 img_unsigned.close()
1173 img_keyblock.close()
1174
David Zeuthen8fecb282017-12-01 16:24:01 -05001175 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001176 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001177 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001178 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001179 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001180 "--partition_size", str(part_size), "--partition_name",
1181 partition_name]
1182 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001183 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001184 if args and args.strip():
1185 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001186 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001187
1188 img.seek(os.SEEK_SET, 0)
1189 data = img.read()
1190
1191 if has_ramdisk:
1192 ramdisk_img.close()
1193 img.close()
1194
1195 return data
1196
1197
Doug Zongkerd5131602012-08-02 14:46:42 -07001198def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001199 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001200 """Return a File object with the desired bootable image.
1201
1202 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1203 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1204 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001205
Doug Zongker55d93282011-01-25 17:03:34 -08001206 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1207 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001208 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001209 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001210
1211 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1212 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001213 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001214 return File.FromLocalFile(name, prebuilt_path)
1215
Tao Bao32fcdab2018-10-12 10:30:39 -07001216 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001217
1218 if info_dict is None:
1219 info_dict = OPTIONS.info_dict
1220
1221 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001222 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1223 # for recovery.
1224 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1225 prebuilt_name != "boot.img" or
1226 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001227
Doug Zongker6f1d0312014-08-22 08:07:12 -07001228 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Mucklef83e3c32020-04-08 18:27:00 -07001229 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001230 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001231 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001232 if data:
1233 return File(name, data)
1234 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001235
Doug Zongkereef39442009-04-02 12:14:19 -07001236
Steve Mucklee1b10862019-07-10 10:49:37 -07001237def _BuildVendorBootImage(sourcedir, info_dict=None):
1238 """Build a vendor boot image from the specified sourcedir.
1239
1240 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1241 turn them into a vendor boot image.
1242
1243 Return the image data, or None if sourcedir does not appear to contains files
1244 for building the requested image.
1245 """
1246
1247 if info_dict is None:
1248 info_dict = OPTIONS.info_dict
1249
1250 img = tempfile.NamedTemporaryFile()
1251
1252 ramdisk_img = _MakeRamdisk(sourcedir)
1253
1254 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1255 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1256
1257 cmd = [mkbootimg]
1258
1259 fn = os.path.join(sourcedir, "dtb")
1260 if os.access(fn, os.F_OK):
1261 cmd.append("--dtb")
1262 cmd.append(fn)
1263
1264 fn = os.path.join(sourcedir, "vendor_cmdline")
1265 if os.access(fn, os.F_OK):
1266 cmd.append("--vendor_cmdline")
1267 cmd.append(open(fn).read().rstrip("\n"))
1268
1269 fn = os.path.join(sourcedir, "base")
1270 if os.access(fn, os.F_OK):
1271 cmd.append("--base")
1272 cmd.append(open(fn).read().rstrip("\n"))
1273
1274 fn = os.path.join(sourcedir, "pagesize")
1275 if os.access(fn, os.F_OK):
1276 cmd.append("--pagesize")
1277 cmd.append(open(fn).read().rstrip("\n"))
1278
1279 args = info_dict.get("mkbootimg_args")
1280 if args and args.strip():
1281 cmd.extend(shlex.split(args))
1282
1283 args = info_dict.get("mkbootimg_version_args")
1284 if args and args.strip():
1285 cmd.extend(shlex.split(args))
1286
1287 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1288 cmd.extend(["--vendor_boot", img.name])
1289
1290 RunAndCheckOutput(cmd)
1291
1292 # AVB: if enabled, calculate and add hash.
1293 if info_dict.get("avb_enable") == "true":
1294 avbtool = info_dict["avb_avbtool"]
1295 part_size = info_dict["vendor_boot_size"]
1296 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001297 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001298 AppendAVBSigningArgs(cmd, "vendor_boot")
1299 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1300 if args and args.strip():
1301 cmd.extend(shlex.split(args))
1302 RunAndCheckOutput(cmd)
1303
1304 img.seek(os.SEEK_SET, 0)
1305 data = img.read()
1306
1307 ramdisk_img.close()
1308 img.close()
1309
1310 return data
1311
1312
1313def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1314 info_dict=None):
1315 """Return a File object with the desired vendor boot image.
1316
1317 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1318 the source files in 'unpack_dir'/'tree_subdir'."""
1319
1320 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1321 if os.path.exists(prebuilt_path):
1322 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1323 return File.FromLocalFile(name, prebuilt_path)
1324
1325 logger.info("building image from target_files %s...", tree_subdir)
1326
1327 if info_dict is None:
1328 info_dict = OPTIONS.info_dict
1329
1330 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1331 if data:
1332 return File(name, data)
1333 return None
1334
1335
Narayan Kamatha07bf042017-08-14 14:49:21 +01001336def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001337 """Gunzips the given gzip compressed file to a given output file."""
1338 with gzip.open(in_filename, "rb") as in_file, \
1339 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001340 shutil.copyfileobj(in_file, out_file)
1341
1342
Tao Bao0ff15de2019-03-20 11:26:06 -07001343def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001344 """Unzips the archive to the given directory.
1345
1346 Args:
1347 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001348 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001349 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1350 archvie. Non-matching patterns will be filtered out. If there's no match
1351 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001352 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001353 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001354 if patterns is not None:
1355 # Filter out non-matching patterns. unzip will complain otherwise.
1356 with zipfile.ZipFile(filename) as input_zip:
1357 names = input_zip.namelist()
1358 filtered = [
1359 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1360
1361 # There isn't any matching files. Don't unzip anything.
1362 if not filtered:
1363 return
1364 cmd.extend(filtered)
1365
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001366 RunAndCheckOutput(cmd)
1367
1368
Doug Zongker75f17362009-12-08 13:46:44 -08001369def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001370 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001371
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001372 Args:
1373 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1374 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1375
1376 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1377 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001378
Tao Bao1c830bf2017-12-25 10:43:47 -08001379 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001380 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001381 """
Doug Zongkereef39442009-04-02 12:14:19 -07001382
Tao Bao1c830bf2017-12-25 10:43:47 -08001383 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001384 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1385 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001386 UnzipToDir(m.group(1), tmp, pattern)
1387 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001388 filename = m.group(1)
1389 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001390 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001391
Tao Baodba59ee2018-01-09 13:21:02 -08001392 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001393
1394
Yifan Hong8a66a712019-04-04 15:37:57 -07001395def GetUserImage(which, tmpdir, input_zip,
1396 info_dict=None,
1397 allow_shared_blocks=None,
1398 hashtree_info_generator=None,
1399 reset_file_map=False):
1400 """Returns an Image object suitable for passing to BlockImageDiff.
1401
1402 This function loads the specified image from the given path. If the specified
1403 image is sparse, it also performs additional processing for OTA purpose. For
1404 example, it always adds block 0 to clobbered blocks list. It also detects
1405 files that cannot be reconstructed from the block list, for whom we should
1406 avoid applying imgdiff.
1407
1408 Args:
1409 which: The partition name.
1410 tmpdir: The directory that contains the prebuilt image and block map file.
1411 input_zip: The target-files ZIP archive.
1412 info_dict: The dict to be looked up for relevant info.
1413 allow_shared_blocks: If image is sparse, whether having shared blocks is
1414 allowed. If none, it is looked up from info_dict.
1415 hashtree_info_generator: If present and image is sparse, generates the
1416 hashtree_info for this sparse image.
1417 reset_file_map: If true and image is sparse, reset file map before returning
1418 the image.
1419 Returns:
1420 A Image object. If it is a sparse image and reset_file_map is False, the
1421 image will have file_map info loaded.
1422 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001423 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001424 info_dict = LoadInfoDict(input_zip)
1425
1426 is_sparse = info_dict.get("extfs_sparse_flag")
1427
1428 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1429 # shared blocks (i.e. some blocks will show up in multiple files' block
1430 # list). We can only allocate such shared blocks to the first "owner", and
1431 # disable imgdiff for all later occurrences.
1432 if allow_shared_blocks is None:
1433 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1434
1435 if is_sparse:
1436 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1437 hashtree_info_generator)
1438 if reset_file_map:
1439 img.ResetFileMap()
1440 return img
1441 else:
1442 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1443
1444
1445def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1446 """Returns a Image object suitable for passing to BlockImageDiff.
1447
1448 This function loads the specified non-sparse image from the given path.
1449
1450 Args:
1451 which: The partition name.
1452 tmpdir: The directory that contains the prebuilt image and block map file.
1453 Returns:
1454 A Image object.
1455 """
1456 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1457 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1458
1459 # The image and map files must have been created prior to calling
1460 # ota_from_target_files.py (since LMP).
1461 assert os.path.exists(path) and os.path.exists(mappath)
1462
Tianjie Xu41976c72019-07-03 13:57:01 -07001463 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1464
Yifan Hong8a66a712019-04-04 15:37:57 -07001465
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001466def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1467 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001468 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1469
1470 This function loads the specified sparse image from the given path, and
1471 performs additional processing for OTA purpose. For example, it always adds
1472 block 0 to clobbered blocks list. It also detects files that cannot be
1473 reconstructed from the block list, for whom we should avoid applying imgdiff.
1474
1475 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001476 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001477 tmpdir: The directory that contains the prebuilt image and block map file.
1478 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001479 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001480 hashtree_info_generator: If present, generates the hashtree_info for this
1481 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001482 Returns:
1483 A SparseImage object, with file_map info loaded.
1484 """
Tao Baoc765cca2018-01-31 17:32:40 -08001485 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1486 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1487
1488 # The image and map files must have been created prior to calling
1489 # ota_from_target_files.py (since LMP).
1490 assert os.path.exists(path) and os.path.exists(mappath)
1491
1492 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1493 # it to clobbered_blocks so that it will be written to the target
1494 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1495 clobbered_blocks = "0"
1496
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001497 image = sparse_img.SparseImage(
1498 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1499 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001500
1501 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1502 # if they contain all zeros. We can't reconstruct such a file from its block
1503 # list. Tag such entries accordingly. (Bug: 65213616)
1504 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001505 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001506 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001507 continue
1508
Tom Cherryd14b8952018-08-09 14:26:00 -07001509 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1510 # filename listed in system.map may contain an additional leading slash
1511 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1512 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001513 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001514
Tom Cherryd14b8952018-08-09 14:26:00 -07001515 # Special handling another case, where files not under /system
1516 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001517 if which == 'system' and not arcname.startswith('SYSTEM'):
1518 arcname = 'ROOT/' + arcname
1519
1520 assert arcname in input_zip.namelist(), \
1521 "Failed to find the ZIP entry for {}".format(entry)
1522
Tao Baoc765cca2018-01-31 17:32:40 -08001523 info = input_zip.getinfo(arcname)
1524 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001525
1526 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001527 # image, check the original block list to determine its completeness. Note
1528 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001529 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001530 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001531
Tao Baoc765cca2018-01-31 17:32:40 -08001532 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1533 ranges.extra['incomplete'] = True
1534
1535 return image
1536
1537
Doug Zongkereef39442009-04-02 12:14:19 -07001538def GetKeyPasswords(keylist):
1539 """Given a list of keys, prompt the user to enter passwords for
1540 those which require them. Return a {key: password} dict. password
1541 will be None if the key has no password."""
1542
Doug Zongker8ce7c252009-05-22 13:34:54 -07001543 no_passwords = []
1544 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001545 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001546 devnull = open("/dev/null", "w+b")
1547 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001548 # We don't need a password for things that aren't really keys.
1549 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001550 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001551 continue
1552
T.R. Fullhart37e10522013-03-18 10:31:26 -07001553 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001554 "-inform", "DER", "-nocrypt"],
1555 stdin=devnull.fileno(),
1556 stdout=devnull.fileno(),
1557 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001558 p.communicate()
1559 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001560 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001561 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001562 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001563 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1564 "-inform", "DER", "-passin", "pass:"],
1565 stdin=devnull.fileno(),
1566 stdout=devnull.fileno(),
1567 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001568 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001569 if p.returncode == 0:
1570 # Encrypted key with empty string as password.
1571 key_passwords[k] = ''
1572 elif stderr.startswith('Error decrypting key'):
1573 # Definitely encrypted key.
1574 # It would have said "Error reading key" if it didn't parse correctly.
1575 need_passwords.append(k)
1576 else:
1577 # Potentially, a type of key that openssl doesn't understand.
1578 # We'll let the routines in signapk.jar handle it.
1579 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001580 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001581
T.R. Fullhart37e10522013-03-18 10:31:26 -07001582 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001583 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001584 return key_passwords
1585
1586
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001587def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001588 """Gets the minSdkVersion declared in the APK.
1589
changho.shin0f125362019-07-08 10:59:00 +09001590 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001591 This can be both a decimal number (API Level) or a codename.
1592
1593 Args:
1594 apk_name: The APK filename.
1595
1596 Returns:
1597 The parsed SDK version string.
1598
1599 Raises:
1600 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001601 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001602 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001603 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001604 stderr=subprocess.PIPE)
1605 stdoutdata, stderrdata = proc.communicate()
1606 if proc.returncode != 0:
1607 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001608 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001609 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001610
Tao Baof47bf0f2018-03-21 23:28:51 -07001611 for line in stdoutdata.split("\n"):
1612 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001613 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1614 if m:
1615 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001616 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001617
1618
1619def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001620 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001621
Tao Baof47bf0f2018-03-21 23:28:51 -07001622 If minSdkVersion is set to a codename, it is translated to a number using the
1623 provided map.
1624
1625 Args:
1626 apk_name: The APK filename.
1627
1628 Returns:
1629 The parsed SDK version number.
1630
1631 Raises:
1632 ExternalError: On failing to get the min SDK version number.
1633 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001634 version = GetMinSdkVersion(apk_name)
1635 try:
1636 return int(version)
1637 except ValueError:
1638 # Not a decimal number. Codename?
1639 if version in codename_to_api_level_map:
1640 return codename_to_api_level_map[version]
1641 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001642 raise ExternalError(
1643 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1644 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001645
1646
1647def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001648 codename_to_api_level_map=None, whole_file=False,
1649 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001650 """Sign the input_name zip/jar/apk, producing output_name. Use the
1651 given key and password (the latter may be None if the key does not
1652 have a password.
1653
Doug Zongker951495f2009-08-14 12:44:19 -07001654 If whole_file is true, use the "-w" option to SignApk to embed a
1655 signature that covers the whole file in the archive comment of the
1656 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001657
1658 min_api_level is the API Level (int) of the oldest platform this file may end
1659 up on. If not specified for an APK, the API Level is obtained by interpreting
1660 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1661
1662 codename_to_api_level_map is needed to translate the codename which may be
1663 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001664
1665 Caller may optionally specify extra args to be passed to SignApk, which
1666 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001667 """
Tao Bao76def242017-11-21 09:25:31 -08001668 if codename_to_api_level_map is None:
1669 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001670 if extra_signapk_args is None:
1671 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001672
Alex Klyubin9667b182015-12-10 13:38:50 -08001673 java_library_path = os.path.join(
1674 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1675
Tao Baoe95540e2016-11-08 12:08:53 -08001676 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1677 ["-Djava.library.path=" + java_library_path,
1678 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001679 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001680 if whole_file:
1681 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001682
1683 min_sdk_version = min_api_level
1684 if min_sdk_version is None:
1685 if not whole_file:
1686 min_sdk_version = GetMinSdkVersionInt(
1687 input_name, codename_to_api_level_map)
1688 if min_sdk_version is not None:
1689 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1690
T.R. Fullhart37e10522013-03-18 10:31:26 -07001691 cmd.extend([key + OPTIONS.public_key_suffix,
1692 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001693 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001694
Tao Bao73dd4f42018-10-04 16:25:33 -07001695 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001696 if password is not None:
1697 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001698 stdoutdata, _ = proc.communicate(password)
1699 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001700 raise ExternalError(
1701 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001702 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001703
Doug Zongkereef39442009-04-02 12:14:19 -07001704
Doug Zongker37974732010-09-16 17:44:38 -07001705def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001706 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001707
Tao Bao9dd909e2017-11-14 11:27:32 -08001708 For non-AVB images, raise exception if the data is too big. Print a warning
1709 if the data is nearing the maximum size.
1710
1711 For AVB images, the actual image size should be identical to the limit.
1712
1713 Args:
1714 data: A string that contains all the data for the partition.
1715 target: The partition name. The ".img" suffix is optional.
1716 info_dict: The dict to be looked up for relevant info.
1717 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001718 if target.endswith(".img"):
1719 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001720 mount_point = "/" + target
1721
Ying Wangf8824af2014-06-03 14:07:27 -07001722 fs_type = None
1723 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001724 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001725 if mount_point == "/userdata":
1726 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001727 p = info_dict["fstab"][mount_point]
1728 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001729 device = p.device
1730 if "/" in device:
1731 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001732 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001733 if not fs_type or not limit:
1734 return
Doug Zongkereef39442009-04-02 12:14:19 -07001735
Andrew Boie0f9aec82012-02-14 09:32:52 -08001736 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001737 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1738 # path.
1739 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1740 if size != limit:
1741 raise ExternalError(
1742 "Mismatching image size for %s: expected %d actual %d" % (
1743 target, limit, size))
1744 else:
1745 pct = float(size) * 100.0 / limit
1746 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1747 if pct >= 99.0:
1748 raise ExternalError(msg)
1749 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001750 logger.warning("\n WARNING: %s\n", msg)
1751 else:
1752 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001753
1754
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001755def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001756 """Parses the APK certs info from a given target-files zip.
1757
1758 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1759 tuple with the following elements: (1) a dictionary that maps packages to
1760 certs (based on the "certificate" and "private_key" attributes in the file;
1761 (2) a string representing the extension of compressed APKs in the target files
1762 (e.g ".gz", ".bro").
1763
1764 Args:
1765 tf_zip: The input target_files ZipFile (already open).
1766
1767 Returns:
1768 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1769 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1770 no compressed APKs.
1771 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001772 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001773 compressed_extension = None
1774
Tao Bao0f990332017-09-08 19:02:54 -07001775 # META/apkcerts.txt contains the info for _all_ the packages known at build
1776 # time. Filter out the ones that are not installed.
1777 installed_files = set()
1778 for name in tf_zip.namelist():
1779 basename = os.path.basename(name)
1780 if basename:
1781 installed_files.add(basename)
1782
Tao Baoda30cfa2017-12-01 16:19:46 -08001783 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001784 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001785 if not line:
1786 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001787 m = re.match(
1788 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham96c9e6e2020-04-03 15:36:23 -07001789 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1790 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001791 line)
1792 if not m:
1793 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001794
Tao Bao818ddf52018-01-05 11:17:34 -08001795 matches = m.groupdict()
1796 cert = matches["CERT"]
1797 privkey = matches["PRIVKEY"]
1798 name = matches["NAME"]
1799 this_compressed_extension = matches["COMPRESSED"]
1800
1801 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1802 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1803 if cert in SPECIAL_CERT_STRINGS and not privkey:
1804 certmap[name] = cert
1805 elif (cert.endswith(OPTIONS.public_key_suffix) and
1806 privkey.endswith(OPTIONS.private_key_suffix) and
1807 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1808 certmap[name] = cert[:-public_key_suffix_len]
1809 else:
1810 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1811
1812 if not this_compressed_extension:
1813 continue
1814
1815 # Only count the installed files.
1816 filename = name + '.' + this_compressed_extension
1817 if filename not in installed_files:
1818 continue
1819
1820 # Make sure that all the values in the compression map have the same
1821 # extension. We don't support multiple compression methods in the same
1822 # system image.
1823 if compressed_extension:
1824 if this_compressed_extension != compressed_extension:
1825 raise ValueError(
1826 "Multiple compressed extensions: {} vs {}".format(
1827 compressed_extension, this_compressed_extension))
1828 else:
1829 compressed_extension = this_compressed_extension
1830
1831 return (certmap,
1832 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001833
1834
Doug Zongkereef39442009-04-02 12:14:19 -07001835COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001836Global options
1837
1838 -p (--path) <dir>
1839 Prepend <dir>/bin to the list of places to search for binaries run by this
1840 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001841
Doug Zongker05d3dea2009-06-22 11:32:31 -07001842 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001843 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001844
Tao Bao30df8b42018-04-23 15:32:53 -07001845 -x (--extra) <key=value>
1846 Add a key/value pair to the 'extras' dict, which device-specific extension
1847 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001848
Doug Zongkereef39442009-04-02 12:14:19 -07001849 -v (--verbose)
1850 Show command lines being executed.
1851
1852 -h (--help)
1853 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001854
1855 --logfile <file>
1856 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001857"""
1858
1859def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001860 print(docstring.rstrip("\n"))
1861 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001862
1863
1864def ParseOptions(argv,
1865 docstring,
1866 extra_opts="", extra_long_opts=(),
1867 extra_option_handler=None):
1868 """Parse the options in argv and return any arguments that aren't
1869 flags. docstring is the calling module's docstring, to be displayed
1870 for errors and -h. extra_opts and extra_long_opts are for flags
1871 defined by the caller, which are processed by passing them to
1872 extra_option_handler."""
1873
1874 try:
1875 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001876 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001877 ["help", "verbose", "path=", "signapk_path=",
1878 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08001879 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001880 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1881 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Dan Austin52903642019-12-12 15:44:00 -08001882 "extra=", "logfile=", "aftl_server=", "aftl_key_path=",
1883 "aftl_manufacturer_key_path=", "aftl_signer_helper="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001884 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001885 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001886 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001887 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001888 sys.exit(2)
1889
Doug Zongkereef39442009-04-02 12:14:19 -07001890 for o, a in opts:
1891 if o in ("-h", "--help"):
1892 Usage(docstring)
1893 sys.exit()
1894 elif o in ("-v", "--verbose"):
1895 OPTIONS.verbose = True
1896 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001897 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001898 elif o in ("--signapk_path",):
1899 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001900 elif o in ("--signapk_shared_library_path",):
1901 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001902 elif o in ("--extra_signapk_args",):
1903 OPTIONS.extra_signapk_args = shlex.split(a)
1904 elif o in ("--java_path",):
1905 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001906 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001907 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08001908 elif o in ("--android_jar_path",):
1909 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001910 elif o in ("--public_key_suffix",):
1911 OPTIONS.public_key_suffix = a
1912 elif o in ("--private_key_suffix",):
1913 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001914 elif o in ("--boot_signer_path",):
1915 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001916 elif o in ("--boot_signer_args",):
1917 OPTIONS.boot_signer_args = shlex.split(a)
1918 elif o in ("--verity_signer_path",):
1919 OPTIONS.verity_signer_path = a
1920 elif o in ("--verity_signer_args",):
1921 OPTIONS.verity_signer_args = shlex.split(a)
Dan Austin52903642019-12-12 15:44:00 -08001922 elif o in ("--aftl_server",):
1923 OPTIONS.aftl_server = a
1924 elif o in ("--aftl_key_path",):
1925 OPTIONS.aftl_key_path = a
1926 elif o in ("--aftl_manufacturer_key_path",):
1927 OPTIONS.aftl_manufacturer_key_path = a
1928 elif o in ("--aftl_signer_helper",):
1929 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07001930 elif o in ("-s", "--device_specific"):
1931 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001932 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001933 key, value = a.split("=", 1)
1934 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001935 elif o in ("--logfile",):
1936 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001937 else:
1938 if extra_option_handler is None or not extra_option_handler(o, a):
1939 assert False, "unknown option \"%s\"" % (o,)
1940
Doug Zongker85448772014-09-09 14:59:20 -07001941 if OPTIONS.search_path:
1942 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1943 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001944
1945 return args
1946
1947
Tao Bao4c851b12016-09-19 13:54:38 -07001948def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001949 """Make a temp file and add it to the list of things to be deleted
1950 when Cleanup() is called. Return the filename."""
1951 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1952 os.close(fd)
1953 OPTIONS.tempfiles.append(fn)
1954 return fn
1955
1956
Tao Bao1c830bf2017-12-25 10:43:47 -08001957def MakeTempDir(prefix='tmp', suffix=''):
1958 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1959
1960 Returns:
1961 The absolute pathname of the new directory.
1962 """
1963 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1964 OPTIONS.tempfiles.append(dir_name)
1965 return dir_name
1966
1967
Doug Zongkereef39442009-04-02 12:14:19 -07001968def Cleanup():
1969 for i in OPTIONS.tempfiles:
1970 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001971 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001972 else:
1973 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001974 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001975
1976
1977class PasswordManager(object):
1978 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001979 self.editor = os.getenv("EDITOR")
1980 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001981
1982 def GetPasswords(self, items):
1983 """Get passwords corresponding to each string in 'items',
1984 returning a dict. (The dict may have keys in addition to the
1985 values in 'items'.)
1986
1987 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1988 user edit that file to add more needed passwords. If no editor is
1989 available, or $ANDROID_PW_FILE isn't define, prompts the user
1990 interactively in the ordinary way.
1991 """
1992
1993 current = self.ReadFile()
1994
1995 first = True
1996 while True:
1997 missing = []
1998 for i in items:
1999 if i not in current or not current[i]:
2000 missing.append(i)
2001 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002002 if not missing:
2003 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002004
2005 for i in missing:
2006 current[i] = ""
2007
2008 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002009 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002010 if sys.version_info[0] >= 3:
2011 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002012 answer = raw_input("try to edit again? [y]> ").strip()
2013 if answer and answer[0] not in 'yY':
2014 raise RuntimeError("key passwords unavailable")
2015 first = False
2016
2017 current = self.UpdateAndReadFile(current)
2018
Dan Albert8b72aef2015-03-23 19:13:21 -07002019 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002020 """Prompt the user to enter a value (password) for each key in
2021 'current' whose value is fales. Returns a new dict with all the
2022 values.
2023 """
2024 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002025 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002026 if v:
2027 result[k] = v
2028 else:
2029 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002030 result[k] = getpass.getpass(
2031 "Enter password for %s key> " % k).strip()
2032 if result[k]:
2033 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002034 return result
2035
2036 def UpdateAndReadFile(self, current):
2037 if not self.editor or not self.pwfile:
2038 return self.PromptResult(current)
2039
2040 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002041 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002042 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2043 f.write("# (Additional spaces are harmless.)\n\n")
2044
2045 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002046 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002047 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002048 f.write("[[[ %s ]]] %s\n" % (v, k))
2049 if not v and first_line is None:
2050 # position cursor on first line with no password.
2051 first_line = i + 4
2052 f.close()
2053
Tao Bao986ee862018-10-04 15:46:16 -07002054 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002055
2056 return self.ReadFile()
2057
2058 def ReadFile(self):
2059 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002060 if self.pwfile is None:
2061 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002062 try:
2063 f = open(self.pwfile, "r")
2064 for line in f:
2065 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002066 if not line or line[0] == '#':
2067 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002068 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2069 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002070 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002071 else:
2072 result[m.group(2)] = m.group(1)
2073 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002074 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002075 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002076 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002077 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002078
2079
Dan Albert8e0178d2015-01-27 15:53:15 -08002080def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2081 compress_type=None):
2082 import datetime
2083
2084 # http://b/18015246
2085 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2086 # for files larger than 2GiB. We can work around this by adjusting their
2087 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2088 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2089 # it isn't clear to me exactly what circumstances cause this).
2090 # `zipfile.write()` must be used directly to work around this.
2091 #
2092 # This mess can be avoided if we port to python3.
2093 saved_zip64_limit = zipfile.ZIP64_LIMIT
2094 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2095
2096 if compress_type is None:
2097 compress_type = zip_file.compression
2098 if arcname is None:
2099 arcname = filename
2100
2101 saved_stat = os.stat(filename)
2102
2103 try:
2104 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2105 # file to be zipped and reset it when we're done.
2106 os.chmod(filename, perms)
2107
2108 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002109 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2110 # intentional. zip stores datetimes in local time without a time zone
2111 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2112 # in the zip archive.
2113 local_epoch = datetime.datetime.fromtimestamp(0)
2114 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002115 os.utime(filename, (timestamp, timestamp))
2116
2117 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2118 finally:
2119 os.chmod(filename, saved_stat.st_mode)
2120 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2121 zipfile.ZIP64_LIMIT = saved_zip64_limit
2122
2123
Tao Bao58c1b962015-05-20 09:32:18 -07002124def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002125 compress_type=None):
2126 """Wrap zipfile.writestr() function to work around the zip64 limit.
2127
2128 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2129 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2130 when calling crc32(bytes).
2131
2132 But it still works fine to write a shorter string into a large zip file.
2133 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2134 when we know the string won't be too long.
2135 """
2136
2137 saved_zip64_limit = zipfile.ZIP64_LIMIT
2138 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2139
2140 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2141 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002142 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002143 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002144 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002145 else:
Tao Baof3282b42015-04-01 11:21:55 -07002146 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002147 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2148 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2149 # such a case (since
2150 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2151 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2152 # permission bits. We follow the logic in Python 3 to get consistent
2153 # behavior between using the two versions.
2154 if not zinfo.external_attr:
2155 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002156
2157 # If compress_type is given, it overrides the value in zinfo.
2158 if compress_type is not None:
2159 zinfo.compress_type = compress_type
2160
Tao Bao58c1b962015-05-20 09:32:18 -07002161 # If perms is given, it has a priority.
2162 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002163 # If perms doesn't set the file type, mark it as a regular file.
2164 if perms & 0o770000 == 0:
2165 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002166 zinfo.external_attr = perms << 16
2167
Tao Baof3282b42015-04-01 11:21:55 -07002168 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002169 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2170
Dan Albert8b72aef2015-03-23 19:13:21 -07002171 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002172 zipfile.ZIP64_LIMIT = saved_zip64_limit
2173
2174
Tao Bao89d7ab22017-12-14 17:05:33 -08002175def ZipDelete(zip_filename, entries):
2176 """Deletes entries from a ZIP file.
2177
2178 Since deleting entries from a ZIP file is not supported, it shells out to
2179 'zip -d'.
2180
2181 Args:
2182 zip_filename: The name of the ZIP file.
2183 entries: The name of the entry, or the list of names to be deleted.
2184
2185 Raises:
2186 AssertionError: In case of non-zero return from 'zip'.
2187 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002188 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002189 entries = [entries]
2190 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002191 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002192
2193
Tao Baof3282b42015-04-01 11:21:55 -07002194def ZipClose(zip_file):
2195 # http://b/18015246
2196 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2197 # central directory.
2198 saved_zip64_limit = zipfile.ZIP64_LIMIT
2199 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2200
2201 zip_file.close()
2202
2203 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002204
2205
2206class DeviceSpecificParams(object):
2207 module = None
2208 def __init__(self, **kwargs):
2209 """Keyword arguments to the constructor become attributes of this
2210 object, which is passed to all functions in the device-specific
2211 module."""
Tao Bao38884282019-07-10 22:20:56 -07002212 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002213 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002214 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002215
2216 if self.module is None:
2217 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002218 if not path:
2219 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002220 try:
2221 if os.path.isdir(path):
2222 info = imp.find_module("releasetools", [path])
2223 else:
2224 d, f = os.path.split(path)
2225 b, x = os.path.splitext(f)
2226 if x == ".py":
2227 f = b
2228 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002229 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002230 self.module = imp.load_module("device_specific", *info)
2231 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002232 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002233
2234 def _DoCall(self, function_name, *args, **kwargs):
2235 """Call the named function in the device-specific module, passing
2236 the given args and kwargs. The first argument to the call will be
2237 the DeviceSpecific object itself. If there is no module, or the
2238 module does not define the function, return the value of the
2239 'default' kwarg (which itself defaults to None)."""
2240 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002241 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002242 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2243
2244 def FullOTA_Assertions(self):
2245 """Called after emitting the block of assertions at the top of a
2246 full OTA package. Implementations can add whatever additional
2247 assertions they like."""
2248 return self._DoCall("FullOTA_Assertions")
2249
Doug Zongkere5ff5902012-01-17 10:55:37 -08002250 def FullOTA_InstallBegin(self):
2251 """Called at the start of full OTA installation."""
2252 return self._DoCall("FullOTA_InstallBegin")
2253
Yifan Hong10c530d2018-12-27 17:34:18 -08002254 def FullOTA_GetBlockDifferences(self):
2255 """Called during full OTA installation and verification.
2256 Implementation should return a list of BlockDifference objects describing
2257 the update on each additional partitions.
2258 """
2259 return self._DoCall("FullOTA_GetBlockDifferences")
2260
Doug Zongker05d3dea2009-06-22 11:32:31 -07002261 def FullOTA_InstallEnd(self):
2262 """Called at the end of full OTA installation; typically this is
2263 used to install the image for the device's baseband processor."""
2264 return self._DoCall("FullOTA_InstallEnd")
2265
2266 def IncrementalOTA_Assertions(self):
2267 """Called after emitting the block of assertions at the top of an
2268 incremental OTA package. Implementations can add whatever
2269 additional assertions they like."""
2270 return self._DoCall("IncrementalOTA_Assertions")
2271
Doug Zongkere5ff5902012-01-17 10:55:37 -08002272 def IncrementalOTA_VerifyBegin(self):
2273 """Called at the start of the verification phase of incremental
2274 OTA installation; additional checks can be placed here to abort
2275 the script before any changes are made."""
2276 return self._DoCall("IncrementalOTA_VerifyBegin")
2277
Doug Zongker05d3dea2009-06-22 11:32:31 -07002278 def IncrementalOTA_VerifyEnd(self):
2279 """Called at the end of the verification phase of incremental OTA
2280 installation; additional checks can be placed here to abort the
2281 script before any changes are made."""
2282 return self._DoCall("IncrementalOTA_VerifyEnd")
2283
Doug Zongkere5ff5902012-01-17 10:55:37 -08002284 def IncrementalOTA_InstallBegin(self):
2285 """Called at the start of incremental OTA installation (after
2286 verification is complete)."""
2287 return self._DoCall("IncrementalOTA_InstallBegin")
2288
Yifan Hong10c530d2018-12-27 17:34:18 -08002289 def IncrementalOTA_GetBlockDifferences(self):
2290 """Called during incremental OTA installation and verification.
2291 Implementation should return a list of BlockDifference objects describing
2292 the update on each additional partitions.
2293 """
2294 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2295
Doug Zongker05d3dea2009-06-22 11:32:31 -07002296 def IncrementalOTA_InstallEnd(self):
2297 """Called at the end of incremental OTA installation; typically
2298 this is used to install the image for the device's baseband
2299 processor."""
2300 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002301
Tao Bao9bc6bb22015-11-09 16:58:28 -08002302 def VerifyOTA_Assertions(self):
2303 return self._DoCall("VerifyOTA_Assertions")
2304
Tao Bao76def242017-11-21 09:25:31 -08002305
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002306class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002307 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002308 self.name = name
2309 self.data = data
2310 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002311 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002312 self.sha1 = sha1(data).hexdigest()
2313
2314 @classmethod
2315 def FromLocalFile(cls, name, diskname):
2316 f = open(diskname, "rb")
2317 data = f.read()
2318 f.close()
2319 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002320
2321 def WriteToTemp(self):
2322 t = tempfile.NamedTemporaryFile()
2323 t.write(self.data)
2324 t.flush()
2325 return t
2326
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002327 def WriteToDir(self, d):
2328 with open(os.path.join(d, self.name), "wb") as fp:
2329 fp.write(self.data)
2330
Geremy Condra36bd3652014-02-06 19:45:10 -08002331 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002332 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002333
Tao Bao76def242017-11-21 09:25:31 -08002334
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002335DIFF_PROGRAM_BY_EXT = {
2336 ".gz" : "imgdiff",
2337 ".zip" : ["imgdiff", "-z"],
2338 ".jar" : ["imgdiff", "-z"],
2339 ".apk" : ["imgdiff", "-z"],
2340 ".img" : "imgdiff",
2341 }
2342
Tao Bao76def242017-11-21 09:25:31 -08002343
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002344class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002345 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002346 self.tf = tf
2347 self.sf = sf
2348 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002349 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002350
2351 def ComputePatch(self):
2352 """Compute the patch (as a string of data) needed to turn sf into
2353 tf. Returns the same tuple as GetPatch()."""
2354
2355 tf = self.tf
2356 sf = self.sf
2357
Doug Zongker24cd2802012-08-14 16:36:15 -07002358 if self.diff_program:
2359 diff_program = self.diff_program
2360 else:
2361 ext = os.path.splitext(tf.name)[1]
2362 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002363
2364 ttemp = tf.WriteToTemp()
2365 stemp = sf.WriteToTemp()
2366
2367 ext = os.path.splitext(tf.name)[1]
2368
2369 try:
2370 ptemp = tempfile.NamedTemporaryFile()
2371 if isinstance(diff_program, list):
2372 cmd = copy.copy(diff_program)
2373 else:
2374 cmd = [diff_program]
2375 cmd.append(stemp.name)
2376 cmd.append(ttemp.name)
2377 cmd.append(ptemp.name)
2378 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002379 err = []
2380 def run():
2381 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002382 if e:
2383 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002384 th = threading.Thread(target=run)
2385 th.start()
2386 th.join(timeout=300) # 5 mins
2387 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002388 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002389 p.terminate()
2390 th.join(5)
2391 if th.is_alive():
2392 p.kill()
2393 th.join()
2394
Tianjie Xua2a9f992018-01-05 15:15:54 -08002395 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002396 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002397 self.patch = None
2398 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002399 diff = ptemp.read()
2400 finally:
2401 ptemp.close()
2402 stemp.close()
2403 ttemp.close()
2404
2405 self.patch = diff
2406 return self.tf, self.sf, self.patch
2407
2408
2409 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002410 """Returns a tuple of (target_file, source_file, patch_data).
2411
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002412 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002413 computing the patch failed.
2414 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002415 return self.tf, self.sf, self.patch
2416
2417
2418def ComputeDifferences(diffs):
2419 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002420 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002421
2422 # Do the largest files first, to try and reduce the long-pole effect.
2423 by_size = [(i.tf.size, i) for i in diffs]
2424 by_size.sort(reverse=True)
2425 by_size = [i[1] for i in by_size]
2426
2427 lock = threading.Lock()
2428 diff_iter = iter(by_size) # accessed under lock
2429
2430 def worker():
2431 try:
2432 lock.acquire()
2433 for d in diff_iter:
2434 lock.release()
2435 start = time.time()
2436 d.ComputePatch()
2437 dur = time.time() - start
2438 lock.acquire()
2439
2440 tf, sf, patch = d.GetPatch()
2441 if sf.name == tf.name:
2442 name = tf.name
2443 else:
2444 name = "%s (%s)" % (tf.name, sf.name)
2445 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002446 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002447 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002448 logger.info(
2449 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2450 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002451 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002452 except Exception:
2453 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002454 raise
2455
2456 # start worker threads; wait for them all to finish.
2457 threads = [threading.Thread(target=worker)
2458 for i in range(OPTIONS.worker_threads)]
2459 for th in threads:
2460 th.start()
2461 while threads:
2462 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002463
2464
Dan Albert8b72aef2015-03-23 19:13:21 -07002465class BlockDifference(object):
2466 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002467 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002468 self.tgt = tgt
2469 self.src = src
2470 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002471 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002472 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002473
Tao Baodd2a5892015-03-12 12:32:37 -07002474 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002475 version = max(
2476 int(i) for i in
2477 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002478 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002479 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002480
Tianjie Xu41976c72019-07-03 13:57:01 -07002481 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2482 version=self.version,
2483 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002484 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002485 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002486 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002487 self.touched_src_ranges = b.touched_src_ranges
2488 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002489
Yifan Hong10c530d2018-12-27 17:34:18 -08002490 # On devices with dynamic partitions, for new partitions,
2491 # src is None but OPTIONS.source_info_dict is not.
2492 if OPTIONS.source_info_dict is None:
2493 is_dynamic_build = OPTIONS.info_dict.get(
2494 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002495 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002496 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002497 is_dynamic_build = OPTIONS.source_info_dict.get(
2498 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002499 is_dynamic_source = partition in shlex.split(
2500 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002501
Yifan Hongbb2658d2019-01-25 12:30:58 -08002502 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002503 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2504
Yifan Hongbb2658d2019-01-25 12:30:58 -08002505 # For dynamic partitions builds, check partition list in both source
2506 # and target build because new partitions may be added, and existing
2507 # partitions may be removed.
2508 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2509
Yifan Hong10c530d2018-12-27 17:34:18 -08002510 if is_dynamic:
2511 self.device = 'map_partition("%s")' % partition
2512 else:
2513 if OPTIONS.source_info_dict is None:
2514 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2515 else:
2516 _, device_path = GetTypeAndDevice("/" + partition,
2517 OPTIONS.source_info_dict)
2518 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002519
Tao Baod8d14be2016-02-04 14:26:02 -08002520 @property
2521 def required_cache(self):
2522 return self._required_cache
2523
Tao Bao76def242017-11-21 09:25:31 -08002524 def WriteScript(self, script, output_zip, progress=None,
2525 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002526 if not self.src:
2527 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002528 script.Print("Patching %s image unconditionally..." % (self.partition,))
2529 else:
2530 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002531
Dan Albert8b72aef2015-03-23 19:13:21 -07002532 if progress:
2533 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002534 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002535
2536 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002537 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002538
Tao Bao9bc6bb22015-11-09 16:58:28 -08002539 def WriteStrictVerifyScript(self, script):
2540 """Verify all the blocks in the care_map, including clobbered blocks.
2541
2542 This differs from the WriteVerifyScript() function: a) it prints different
2543 error messages; b) it doesn't allow half-way updated images to pass the
2544 verification."""
2545
2546 partition = self.partition
2547 script.Print("Verifying %s..." % (partition,))
2548 ranges = self.tgt.care_map
2549 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002550 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002551 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2552 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002553 self.device, ranges_str,
2554 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002555 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002556 script.AppendExtra("")
2557
Tao Baod522bdc2016-04-12 15:53:16 -07002558 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002559 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002560
2561 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002562 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002563 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002564
2565 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002566 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002567 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002568 ranges = self.touched_src_ranges
2569 expected_sha1 = self.touched_src_sha1
2570 else:
2571 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2572 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002573
2574 # No blocks to be checked, skipping.
2575 if not ranges:
2576 return
2577
Tao Bao5ece99d2015-05-12 11:42:31 -07002578 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002579 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002580 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002581 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2582 '"%s.patch.dat")) then' % (
2583 self.device, ranges_str, expected_sha1,
2584 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002585 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002586 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002587
Tianjie Xufc3422a2015-12-15 11:53:59 -08002588 if self.version >= 4:
2589
2590 # Bug: 21124327
2591 # When generating incrementals for the system and vendor partitions in
2592 # version 4 or newer, explicitly check the first block (which contains
2593 # the superblock) of the partition to see if it's what we expect. If
2594 # this check fails, give an explicit log message about the partition
2595 # having been remounted R/W (the most likely explanation).
2596 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002597 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002598
2599 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002600 if partition == "system":
2601 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2602 else:
2603 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002604 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002605 'ifelse (block_image_recover({device}, "{ranges}") && '
2606 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002607 'package_extract_file("{partition}.transfer.list"), '
2608 '"{partition}.new.dat", "{partition}.patch.dat"), '
2609 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002610 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002611 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002612 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002613
Tao Baodd2a5892015-03-12 12:32:37 -07002614 # Abort the OTA update. Note that the incremental OTA cannot be applied
2615 # even if it may match the checksum of the target partition.
2616 # a) If version < 3, operations like move and erase will make changes
2617 # unconditionally and damage the partition.
2618 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002619 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002620 if partition == "system":
2621 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2622 else:
2623 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2624 script.AppendExtra((
2625 'abort("E%d: %s partition has unexpected contents");\n'
2626 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002627
Yifan Hong10c530d2018-12-27 17:34:18 -08002628 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002629 partition = self.partition
2630 script.Print('Verifying the updated %s image...' % (partition,))
2631 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2632 ranges = self.tgt.care_map
2633 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002634 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002635 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002636 self.device, ranges_str,
2637 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002638
2639 # Bug: 20881595
2640 # Verify that extended blocks are really zeroed out.
2641 if self.tgt.extended:
2642 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002643 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002644 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002645 self.device, ranges_str,
2646 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002647 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002648 if partition == "system":
2649 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2650 else:
2651 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002652 script.AppendExtra(
2653 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002654 ' abort("E%d: %s partition has unexpected non-zero contents after '
2655 'OTA update");\n'
2656 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002657 else:
2658 script.Print('Verified the updated %s image.' % (partition,))
2659
Tianjie Xu209db462016-05-24 17:34:52 -07002660 if partition == "system":
2661 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2662 else:
2663 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2664
Tao Bao5fcaaef2015-06-01 13:40:49 -07002665 script.AppendExtra(
2666 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002667 ' abort("E%d: %s partition has unexpected contents after OTA '
2668 'update");\n'
2669 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002670
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002671 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002672 ZipWrite(output_zip,
2673 '{}.transfer.list'.format(self.path),
2674 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002675
Tao Bao76def242017-11-21 09:25:31 -08002676 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2677 # its size. Quailty 9 almost triples the compression time but doesn't
2678 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002679 # zip | brotli(quality 6) | brotli(quality 9)
2680 # compressed_size: 942M | 869M (~8% reduced) | 854M
2681 # compression_time: 75s | 265s | 719s
2682 # decompression_time: 15s | 25s | 25s
2683
2684 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002685 brotli_cmd = ['brotli', '--quality=6',
2686 '--output={}.new.dat.br'.format(self.path),
2687 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002688 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002689 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002690
2691 new_data_name = '{}.new.dat.br'.format(self.partition)
2692 ZipWrite(output_zip,
2693 '{}.new.dat.br'.format(self.path),
2694 new_data_name,
2695 compress_type=zipfile.ZIP_STORED)
2696 else:
2697 new_data_name = '{}.new.dat'.format(self.partition)
2698 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2699
Dan Albert8e0178d2015-01-27 15:53:15 -08002700 ZipWrite(output_zip,
2701 '{}.patch.dat'.format(self.path),
2702 '{}.patch.dat'.format(self.partition),
2703 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002704
Tianjie Xu209db462016-05-24 17:34:52 -07002705 if self.partition == "system":
2706 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2707 else:
2708 code = ErrorCode.VENDOR_UPDATE_FAILURE
2709
Yifan Hong10c530d2018-12-27 17:34:18 -08002710 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002711 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002712 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002713 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002714 device=self.device, partition=self.partition,
2715 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002716 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002717
Dan Albert8b72aef2015-03-23 19:13:21 -07002718 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002719 data = source.ReadRangeSet(ranges)
2720 ctx = sha1()
2721
2722 for p in data:
2723 ctx.update(p)
2724
2725 return ctx.hexdigest()
2726
Tao Baoe9b61912015-07-09 17:37:49 -07002727 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2728 """Return the hash value for all zero blocks."""
2729 zero_block = '\x00' * 4096
2730 ctx = sha1()
2731 for _ in range(num_blocks):
2732 ctx.update(zero_block)
2733
2734 return ctx.hexdigest()
2735
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002736
Tianjie Xu41976c72019-07-03 13:57:01 -07002737# Expose these two classes to support vendor-specific scripts
2738DataImage = images.DataImage
2739EmptyImage = images.EmptyImage
2740
Tao Bao76def242017-11-21 09:25:31 -08002741
Doug Zongker96a57e72010-09-26 14:57:41 -07002742# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002743PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002744 "ext4": "EMMC",
2745 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002746 "f2fs": "EMMC",
2747 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002748}
Doug Zongker96a57e72010-09-26 14:57:41 -07002749
Tao Bao76def242017-11-21 09:25:31 -08002750
Doug Zongker96a57e72010-09-26 14:57:41 -07002751def GetTypeAndDevice(mount_point, info):
2752 fstab = info["fstab"]
2753 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002754 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2755 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002756 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002757 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002758
2759
2760def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002761 """Parses and converts a PEM-encoded certificate into DER-encoded.
2762
2763 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2764
2765 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002766 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002767 """
2768 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002769 save = False
2770 for line in data.split("\n"):
2771 if "--END CERTIFICATE--" in line:
2772 break
2773 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002774 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002775 if "--BEGIN CERTIFICATE--" in line:
2776 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002777 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002778 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002779
Tao Bao04e1f012018-02-04 12:13:35 -08002780
2781def ExtractPublicKey(cert):
2782 """Extracts the public key (PEM-encoded) from the given certificate file.
2783
2784 Args:
2785 cert: The certificate filename.
2786
2787 Returns:
2788 The public key string.
2789
2790 Raises:
2791 AssertionError: On non-zero return from 'openssl'.
2792 """
2793 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2794 # While openssl 1.1 writes the key into the given filename followed by '-out',
2795 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2796 # stdout instead.
2797 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2798 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2799 pubkey, stderrdata = proc.communicate()
2800 assert proc.returncode == 0, \
2801 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2802 return pubkey
2803
2804
Tao Bao1ac886e2019-06-26 11:58:22 -07002805def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002806 """Extracts the AVB public key from the given public or private key.
2807
2808 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002809 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002810 key: The input key file, which should be PEM-encoded public or private key.
2811
2812 Returns:
2813 The path to the extracted AVB public key file.
2814 """
2815 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2816 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002817 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002818 return output
2819
2820
Doug Zongker412c02f2014-02-13 10:58:24 -08002821def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2822 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002823 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002824
Tao Bao6d5d6232018-03-09 17:04:42 -08002825 Most of the space in the boot and recovery images is just the kernel, which is
2826 identical for the two, so the resulting patch should be efficient. Add it to
2827 the output zip, along with a shell script that is run from init.rc on first
2828 boot to actually do the patching and install the new recovery image.
2829
2830 Args:
2831 input_dir: The top-level input directory of the target-files.zip.
2832 output_sink: The callback function that writes the result.
2833 recovery_img: File object for the recovery image.
2834 boot_img: File objects for the boot image.
2835 info_dict: A dict returned by common.LoadInfoDict() on the input
2836 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002837 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002838 if info_dict is None:
2839 info_dict = OPTIONS.info_dict
2840
Tao Bao6d5d6232018-03-09 17:04:42 -08002841 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002842 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2843
2844 if board_uses_vendorimage:
2845 # In this case, the output sink is rooted at VENDOR
2846 recovery_img_path = "etc/recovery.img"
2847 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2848 sh_dir = "bin"
2849 else:
2850 # In this case the output sink is rooted at SYSTEM
2851 recovery_img_path = "vendor/etc/recovery.img"
2852 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2853 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002854
Tao Baof2cffbd2015-07-22 12:33:18 -07002855 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002856 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002857
2858 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002859 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002860 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002861 # With system-root-image, boot and recovery images will have mismatching
2862 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2863 # to handle such a case.
2864 if system_root_image:
2865 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002866 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002867 assert not os.path.exists(path)
2868 else:
2869 diff_program = ["imgdiff"]
2870 if os.path.exists(path):
2871 diff_program.append("-b")
2872 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002873 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002874 else:
2875 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002876
2877 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2878 _, _, patch = d.ComputePatch()
2879 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002880
Dan Albertebb19aa2015-03-27 19:11:53 -07002881 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002882 # The following GetTypeAndDevice()s need to use the path in the target
2883 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002884 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2885 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2886 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002887 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002888
Tao Baof2cffbd2015-07-22 12:33:18 -07002889 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002890
2891 # Note that we use /vendor to refer to the recovery resources. This will
2892 # work for a separate vendor partition mounted at /vendor or a
2893 # /system/vendor subdirectory on the system partition, for which init will
2894 # create a symlink from /vendor to /system/vendor.
2895
2896 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002897if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2898 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002899 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002900 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2901 log -t recovery "Installing new recovery image: succeeded" || \\
2902 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002903else
2904 log -t recovery "Recovery image already installed"
2905fi
2906""" % {'type': recovery_type,
2907 'device': recovery_device,
2908 'sha1': recovery_img.sha1,
2909 'size': recovery_img.size}
2910 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002911 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002912if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2913 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002914 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002915 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2916 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2917 log -t recovery "Installing new recovery image: succeeded" || \\
2918 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002919else
2920 log -t recovery "Recovery image already installed"
2921fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002922""" % {'boot_size': boot_img.size,
2923 'boot_sha1': boot_img.sha1,
2924 'recovery_size': recovery_img.size,
2925 'recovery_sha1': recovery_img.sha1,
2926 'boot_type': boot_type,
2927 'boot_device': boot_device,
2928 'recovery_type': recovery_type,
2929 'recovery_device': recovery_device,
2930 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002931
Bill Peckhame868aec2019-09-17 17:06:47 -07002932 # The install script location moved from /system/etc to /system/bin in the L
2933 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2934 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002935
Tao Bao32fcdab2018-10-12 10:30:39 -07002936 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002937
Tao Baoda30cfa2017-12-01 16:19:46 -08002938 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002939
2940
2941class DynamicPartitionUpdate(object):
2942 def __init__(self, src_group=None, tgt_group=None, progress=None,
2943 block_difference=None):
2944 self.src_group = src_group
2945 self.tgt_group = tgt_group
2946 self.progress = progress
2947 self.block_difference = block_difference
2948
2949 @property
2950 def src_size(self):
2951 if not self.block_difference:
2952 return 0
2953 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2954
2955 @property
2956 def tgt_size(self):
2957 if not self.block_difference:
2958 return 0
2959 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2960
2961 @staticmethod
2962 def _GetSparseImageSize(img):
2963 if not img:
2964 return 0
2965 return img.blocksize * img.total_blocks
2966
2967
2968class DynamicGroupUpdate(object):
2969 def __init__(self, src_size=None, tgt_size=None):
2970 # None: group does not exist. 0: no size limits.
2971 self.src_size = src_size
2972 self.tgt_size = tgt_size
2973
2974
2975class DynamicPartitionsDifference(object):
2976 def __init__(self, info_dict, block_diffs, progress_dict=None,
2977 source_info_dict=None):
2978 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002979 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002980
2981 self._remove_all_before_apply = False
2982 if source_info_dict is None:
2983 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002984 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002985
Tao Baof1113e92019-06-18 12:10:14 -07002986 block_diff_dict = collections.OrderedDict(
2987 [(e.partition, e) for e in block_diffs])
2988
Yifan Hong10c530d2018-12-27 17:34:18 -08002989 assert len(block_diff_dict) == len(block_diffs), \
2990 "Duplicated BlockDifference object for {}".format(
2991 [partition for partition, count in
2992 collections.Counter(e.partition for e in block_diffs).items()
2993 if count > 1])
2994
Yifan Hong79997e52019-01-23 16:56:19 -08002995 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002996
2997 for p, block_diff in block_diff_dict.items():
2998 self._partition_updates[p] = DynamicPartitionUpdate()
2999 self._partition_updates[p].block_difference = block_diff
3000
3001 for p, progress in progress_dict.items():
3002 if p in self._partition_updates:
3003 self._partition_updates[p].progress = progress
3004
3005 tgt_groups = shlex.split(info_dict.get(
3006 "super_partition_groups", "").strip())
3007 src_groups = shlex.split(source_info_dict.get(
3008 "super_partition_groups", "").strip())
3009
3010 for g in tgt_groups:
3011 for p in shlex.split(info_dict.get(
3012 "super_%s_partition_list" % g, "").strip()):
3013 assert p in self._partition_updates, \
3014 "{} is in target super_{}_partition_list but no BlockDifference " \
3015 "object is provided.".format(p, g)
3016 self._partition_updates[p].tgt_group = g
3017
3018 for g in src_groups:
3019 for p in shlex.split(source_info_dict.get(
3020 "super_%s_partition_list" % g, "").strip()):
3021 assert p in self._partition_updates, \
3022 "{} is in source super_{}_partition_list but no BlockDifference " \
3023 "object is provided.".format(p, g)
3024 self._partition_updates[p].src_group = g
3025
Yifan Hong45433e42019-01-18 13:55:25 -08003026 target_dynamic_partitions = set(shlex.split(info_dict.get(
3027 "dynamic_partition_list", "").strip()))
3028 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3029 if u.tgt_size)
3030 assert block_diffs_with_target == target_dynamic_partitions, \
3031 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3032 list(target_dynamic_partitions), list(block_diffs_with_target))
3033
3034 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3035 "dynamic_partition_list", "").strip()))
3036 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3037 if u.src_size)
3038 assert block_diffs_with_source == source_dynamic_partitions, \
3039 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3040 list(source_dynamic_partitions), list(block_diffs_with_source))
3041
Yifan Hong10c530d2018-12-27 17:34:18 -08003042 if self._partition_updates:
3043 logger.info("Updating dynamic partitions %s",
3044 self._partition_updates.keys())
3045
Yifan Hong79997e52019-01-23 16:56:19 -08003046 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003047
3048 for g in tgt_groups:
3049 self._group_updates[g] = DynamicGroupUpdate()
3050 self._group_updates[g].tgt_size = int(info_dict.get(
3051 "super_%s_group_size" % g, "0").strip())
3052
3053 for g in src_groups:
3054 if g not in self._group_updates:
3055 self._group_updates[g] = DynamicGroupUpdate()
3056 self._group_updates[g].src_size = int(source_info_dict.get(
3057 "super_%s_group_size" % g, "0").strip())
3058
3059 self._Compute()
3060
3061 def WriteScript(self, script, output_zip, write_verify_script=False):
3062 script.Comment('--- Start patching dynamic partitions ---')
3063 for p, u in self._partition_updates.items():
3064 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3065 script.Comment('Patch partition %s' % p)
3066 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3067 write_verify_script=False)
3068
3069 op_list_path = MakeTempFile()
3070 with open(op_list_path, 'w') as f:
3071 for line in self._op_list:
3072 f.write('{}\n'.format(line))
3073
3074 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3075
3076 script.Comment('Update dynamic partition metadata')
3077 script.AppendExtra('assert(update_dynamic_partitions('
3078 'package_extract_file("dynamic_partitions_op_list")));')
3079
3080 if write_verify_script:
3081 for p, u in self._partition_updates.items():
3082 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3083 u.block_difference.WritePostInstallVerifyScript(script)
3084 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3085
3086 for p, u in self._partition_updates.items():
3087 if u.tgt_size and u.src_size <= u.tgt_size:
3088 script.Comment('Patch partition %s' % p)
3089 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3090 write_verify_script=write_verify_script)
3091 if write_verify_script:
3092 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3093
3094 script.Comment('--- End patching dynamic partitions ---')
3095
3096 def _Compute(self):
3097 self._op_list = list()
3098
3099 def append(line):
3100 self._op_list.append(line)
3101
3102 def comment(line):
3103 self._op_list.append("# %s" % line)
3104
3105 if self._remove_all_before_apply:
3106 comment('Remove all existing dynamic partitions and groups before '
3107 'applying full OTA')
3108 append('remove_all_groups')
3109
3110 for p, u in self._partition_updates.items():
3111 if u.src_group and not u.tgt_group:
3112 append('remove %s' % p)
3113
3114 for p, u in self._partition_updates.items():
3115 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3116 comment('Move partition %s from %s to default' % (p, u.src_group))
3117 append('move %s default' % p)
3118
3119 for p, u in self._partition_updates.items():
3120 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3121 comment('Shrink partition %s from %d to %d' %
3122 (p, u.src_size, u.tgt_size))
3123 append('resize %s %s' % (p, u.tgt_size))
3124
3125 for g, u in self._group_updates.items():
3126 if u.src_size is not None and u.tgt_size is None:
3127 append('remove_group %s' % g)
3128 if (u.src_size is not None and u.tgt_size is not None and
3129 u.src_size > u.tgt_size):
3130 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3131 append('resize_group %s %d' % (g, u.tgt_size))
3132
3133 for g, u in self._group_updates.items():
3134 if u.src_size is None and u.tgt_size is not None:
3135 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3136 append('add_group %s %d' % (g, u.tgt_size))
3137 if (u.src_size is not None and u.tgt_size is not None and
3138 u.src_size < u.tgt_size):
3139 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3140 append('resize_group %s %d' % (g, u.tgt_size))
3141
3142 for p, u in self._partition_updates.items():
3143 if u.tgt_group and not u.src_group:
3144 comment('Add partition %s to group %s' % (p, u.tgt_group))
3145 append('add %s %s' % (p, u.tgt_group))
3146
3147 for p, u in self._partition_updates.items():
3148 if u.tgt_size and u.src_size < u.tgt_size:
3149 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3150 append('resize %s %d' % (p, u.tgt_size))
3151
3152 for p, u in self._partition_updates.items():
3153 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3154 comment('Move partition %s from default to %s' %
3155 (p, u.tgt_group))
3156 append('move %s %s' % (p, u.tgt_group))