blob: 2f89a70957178b5e1a554d1b4a9d808e29bf2ce7 [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 = []
Tianjie0f307452020-04-01 12:20:21 -070080 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080081 self.aftl_server = None
82 self.aftl_key_path = None
83 self.aftl_manufacturer_key_path = None
84 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070085 self.verbose = False
86 self.tempfiles = []
87 self.device_specific = None
88 self.extras = {}
89 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070090 self.source_info_dict = None
91 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070092 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070093 # Stash size cannot exceed cache_size * threshold.
94 self.cache_size = None
95 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070096 self.logfile = None
Dan Albert8b72aef2015-03-23 19:13:21 -070097
98
99OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700100
Tao Bao71197512018-10-11 14:08:45 -0700101# The block size that's used across the releasetools scripts.
102BLOCK_SIZE = 4096
103
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800104# Values for "certificate" in apkcerts that mean special things.
105SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
106
Tao Bao5cc0abb2019-03-21 10:18:05 -0700107# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
108# that system_other is not in the list because we don't want to include its
109# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900110AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Steve Mucklee1b10862019-07-10 10:49:37 -0700111 'system_ext', 'vendor', 'vendor_boot')
Tao Bao9dd909e2017-11-14 11:27:32 -0800112
Tao Bao08c190f2019-06-03 23:07:58 -0700113# Chained VBMeta partitions.
114AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
115
Tianjie Xu861f4132018-09-12 11:49:33 -0700116# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900117PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700118
119
Tianjie Xu209db462016-05-24 17:34:52 -0700120class ErrorCode(object):
121 """Define error_codes for failures that happen during the actual
122 update package installation.
123
124 Error codes 0-999 are reserved for failures before the package
125 installation (i.e. low battery, package verification failure).
126 Detailed code in 'bootable/recovery/error_code.h' """
127
128 SYSTEM_VERIFICATION_FAILURE = 1000
129 SYSTEM_UPDATE_FAILURE = 1001
130 SYSTEM_UNEXPECTED_CONTENTS = 1002
131 SYSTEM_NONZERO_CONTENTS = 1003
132 SYSTEM_RECOVER_FAILURE = 1004
133 VENDOR_VERIFICATION_FAILURE = 2000
134 VENDOR_UPDATE_FAILURE = 2001
135 VENDOR_UNEXPECTED_CONTENTS = 2002
136 VENDOR_NONZERO_CONTENTS = 2003
137 VENDOR_RECOVER_FAILURE = 2004
138 OEM_PROP_MISMATCH = 3000
139 FINGERPRINT_MISMATCH = 3001
140 THUMBPRINT_MISMATCH = 3002
141 OLDER_BUILD = 3003
142 DEVICE_MISMATCH = 3004
143 BAD_PATCH_FILE = 3005
144 INSUFFICIENT_CACHE_SPACE = 3006
145 TUNE_PARTITION_FAILURE = 3007
146 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800147
Tao Bao80921982018-03-21 21:02:19 -0700148
Dan Albert8b72aef2015-03-23 19:13:21 -0700149class ExternalError(RuntimeError):
150 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700151
152
Tao Bao32fcdab2018-10-12 10:30:39 -0700153def InitLogging():
154 DEFAULT_LOGGING_CONFIG = {
155 'version': 1,
156 'disable_existing_loggers': False,
157 'formatters': {
158 'standard': {
159 'format':
160 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
161 'datefmt': '%Y-%m-%d %H:%M:%S',
162 },
163 },
164 'handlers': {
165 'default': {
166 'class': 'logging.StreamHandler',
167 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700168 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700169 },
170 },
171 'loggers': {
172 '': {
173 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700174 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700175 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700176 }
177 }
178 }
179 env_config = os.getenv('LOGGING_CONFIG')
180 if env_config:
181 with open(env_config) as f:
182 config = json.load(f)
183 else:
184 config = DEFAULT_LOGGING_CONFIG
185
186 # Increase the logging level for verbose mode.
187 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700188 config = copy.deepcopy(config)
189 config['handlers']['default']['level'] = 'INFO'
190
191 if OPTIONS.logfile:
192 config = copy.deepcopy(config)
193 config['handlers']['logfile'] = {
194 'class': 'logging.FileHandler',
195 'formatter': 'standard',
196 'level': 'INFO',
197 'mode': 'w',
198 'filename': OPTIONS.logfile,
199 }
200 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700201
202 logging.config.dictConfig(config)
203
204
Tao Bao39451582017-05-04 11:10:47 -0700205def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700206 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700207
Tao Bao73dd4f42018-10-04 16:25:33 -0700208 Args:
209 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700210 verbose: Whether the commands should be shown. Default to the global
211 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700212 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
213 stdin, etc. stdout and stderr will default to subprocess.PIPE and
214 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800215 universal_newlines will default to True, as most of the users in
216 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700217
218 Returns:
219 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700220 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700221 if 'stdout' not in kwargs and 'stderr' not in kwargs:
222 kwargs['stdout'] = subprocess.PIPE
223 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800224 if 'universal_newlines' not in kwargs:
225 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700226 # Don't log any if caller explicitly says so.
227 if verbose != False:
228 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700229 return subprocess.Popen(args, **kwargs)
230
231
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800232def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800233 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800234
235 Args:
236 args: The command represented as a list of strings.
237 verbose: Whether the commands should be shown. Default to the global
238 verbosity if unspecified.
239 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
240 stdin, etc. stdout and stderr will default to subprocess.PIPE and
241 subprocess.STDOUT respectively unless caller specifies any of them.
242
Bill Peckham889b0c62019-02-21 18:53:37 -0800243 Raises:
244 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800245 """
246 proc = Run(args, verbose=verbose, **kwargs)
247 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800248
249 if proc.returncode != 0:
250 raise ExternalError(
251 "Failed to run command '{}' (exit code {})".format(
252 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800253
254
Tao Bao986ee862018-10-04 15:46:16 -0700255def RunAndCheckOutput(args, verbose=None, **kwargs):
256 """Runs the given command and returns the output.
257
258 Args:
259 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700260 verbose: Whether the commands should be shown. Default to the global
261 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700262 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
263 stdin, etc. stdout and stderr will default to subprocess.PIPE and
264 subprocess.STDOUT respectively unless caller specifies any of them.
265
266 Returns:
267 The output string.
268
269 Raises:
270 ExternalError: On non-zero exit from the command.
271 """
Tao Bao986ee862018-10-04 15:46:16 -0700272 proc = Run(args, verbose=verbose, **kwargs)
273 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800274 if output is None:
275 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700276 # Don't log any if caller explicitly says so.
277 if verbose != False:
278 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700279 if proc.returncode != 0:
280 raise ExternalError(
281 "Failed to run command '{}' (exit code {}):\n{}".format(
282 args, proc.returncode, output))
283 return output
284
285
Tao Baoc765cca2018-01-31 17:32:40 -0800286def RoundUpTo4K(value):
287 rounded_up = value + 4095
288 return rounded_up - (rounded_up % 4096)
289
290
Ying Wang7e6d4e42010-12-13 16:25:36 -0800291def CloseInheritedPipes():
292 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
293 before doing other work."""
294 if platform.system() != "Darwin":
295 return
296 for d in range(3, 1025):
297 try:
298 stat = os.fstat(d)
299 if stat is not None:
300 pipebit = stat[0] & 0x1000
301 if pipebit != 0:
302 os.close(d)
303 except OSError:
304 pass
305
306
Tao Bao1c320f82019-10-04 23:25:12 -0700307class BuildInfo(object):
308 """A class that holds the information for a given build.
309
310 This class wraps up the property querying for a given source or target build.
311 It abstracts away the logic of handling OEM-specific properties, and caches
312 the commonly used properties such as fingerprint.
313
314 There are two types of info dicts: a) build-time info dict, which is generated
315 at build time (i.e. included in a target_files zip); b) OEM info dict that is
316 specified at package generation time (via command line argument
317 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
318 having "oem_fingerprint_properties" in build-time info dict), all the queries
319 would be answered based on build-time info dict only. Otherwise if using
320 OEM-specific properties, some of them will be calculated from two info dicts.
321
322 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800323 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700324
325 Attributes:
326 info_dict: The build-time info dict.
327 is_ab: Whether it's a build that uses A/B OTA.
328 oem_dicts: A list of OEM dicts.
329 oem_props: A list of OEM properties that should be read from OEM dicts; None
330 if the build doesn't use any OEM-specific property.
331 fingerprint: The fingerprint of the build, which would be calculated based
332 on OEM properties if applicable.
333 device: The device name, which could come from OEM dicts if applicable.
334 """
335
336 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
337 "ro.product.manufacturer", "ro.product.model",
338 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700339 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
340 "product", "odm", "vendor", "system_ext", "system"]
341 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
342 "product", "product_services", "odm", "vendor", "system"]
343 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700344
Tao Bao3ed35d32019-10-07 20:48:48 -0700345 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700346 """Initializes a BuildInfo instance with the given dicts.
347
348 Note that it only wraps up the given dicts, without making copies.
349
350 Arguments:
351 info_dict: The build-time info dict.
352 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
353 that it always uses the first dict to calculate the fingerprint or the
354 device name. The rest would be used for asserting OEM properties only
355 (e.g. one package can be installed on one of these devices).
356
357 Raises:
358 ValueError: On invalid inputs.
359 """
360 self.info_dict = info_dict
361 self.oem_dicts = oem_dicts
362
363 self._is_ab = info_dict.get("ab_update") == "true"
364 self._oem_props = info_dict.get("oem_fingerprint_properties")
365
366 if self._oem_props:
367 assert oem_dicts, "OEM source required for this build"
368
Daniel Normand5fe8622020-01-08 17:01:11 -0800369 def check_fingerprint(fingerprint):
370 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
371 raise ValueError(
372 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
373 "3.2.2. Build Parameters.".format(fingerprint))
374
375
376 self._partition_fingerprints = {}
377 for partition in PARTITIONS_WITH_CARE_MAP:
378 try:
379 fingerprint = self.CalculatePartitionFingerprint(partition)
380 check_fingerprint(fingerprint)
381 self._partition_fingerprints[partition] = fingerprint
382 except ExternalError:
383 continue
384 if "system" in self._partition_fingerprints:
385 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
386 # need a fingerprint when creating the image.
387 self._partition_fingerprints[
388 "system_other"] = self._partition_fingerprints["system"]
389
Tao Bao1c320f82019-10-04 23:25:12 -0700390 # These two should be computed only after setting self._oem_props.
391 self._device = self.GetOemProperty("ro.product.device")
392 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800393 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700394
395 @property
396 def is_ab(self):
397 return self._is_ab
398
399 @property
400 def device(self):
401 return self._device
402
403 @property
404 def fingerprint(self):
405 return self._fingerprint
406
407 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700408 def oem_props(self):
409 return self._oem_props
410
411 def __getitem__(self, key):
412 return self.info_dict[key]
413
414 def __setitem__(self, key, value):
415 self.info_dict[key] = value
416
417 def get(self, key, default=None):
418 return self.info_dict.get(key, default)
419
420 def items(self):
421 return self.info_dict.items()
422
Daniel Normand5fe8622020-01-08 17:01:11 -0800423 def GetPartitionBuildProp(self, prop, partition):
424 """Returns the inquired build property for the provided partition."""
425 # If provided a partition for this property, only look within that
426 # partition's build.prop.
427 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
428 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
429 else:
430 prop = prop.replace("ro.", "ro.{}.".format(partition))
431 try:
432 return self.info_dict.get("{}.build.prop".format(partition), {})[prop]
433 except KeyError:
434 raise ExternalError("couldn't find %s in %s.build.prop" %
435 (prop, partition))
436
Tao Bao1c320f82019-10-04 23:25:12 -0700437 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800438 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700439 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
440 return self._ResolveRoProductBuildProp(prop)
441
442 try:
443 return self.info_dict.get("build.prop", {})[prop]
444 except KeyError:
445 raise ExternalError("couldn't find %s in build.prop" % (prop,))
446
447 def _ResolveRoProductBuildProp(self, prop):
448 """Resolves the inquired ro.product.* build property"""
449 prop_val = self.info_dict.get("build.prop", {}).get(prop)
450 if prop_val:
451 return prop_val
452
Steven Laver8e2086e2020-04-27 16:26:31 -0700453 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tao Bao1c320f82019-10-04 23:25:12 -0700454 source_order_val = self.info_dict.get("build.prop", {}).get(
455 "ro.product.property_source_order")
456 if source_order_val:
457 source_order = source_order_val.split(",")
458 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700459 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700460
461 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700462 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700463 raise ExternalError(
464 "Invalid ro.product.property_source_order '{}'".format(source_order))
465
466 for source in source_order:
467 source_prop = prop.replace(
468 "ro.product", "ro.product.{}".format(source), 1)
469 prop_val = self.info_dict.get(
470 "{}.build.prop".format(source), {}).get(source_prop)
471 if prop_val:
472 return prop_val
473
474 raise ExternalError("couldn't resolve {}".format(prop))
475
Steven Laver8e2086e2020-04-27 16:26:31 -0700476 def _GetRoProductPropsDefaultSourceOrder(self):
477 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
478 # values of these properties for each Android release.
479 android_codename = self.info_dict.get("build.prop", {}).get(
480 "ro.build.version.codename")
481 if android_codename == "REL":
482 android_version = self.info_dict.get("build.prop", {}).get(
483 "ro.build.version.release")
484 if android_version == "10":
485 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
486 # NOTE: float() conversion of android_version will have rounding error.
487 # We are checking for "9" or less, and using "< 10" is well outside of
488 # possible floating point rounding.
489 try:
490 android_version_val = float(android_version)
491 except ValueError:
492 android_version_val = 0
493 if android_version_val < 10:
494 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
495 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
496
Tao Bao1c320f82019-10-04 23:25:12 -0700497 def GetOemProperty(self, key):
498 if self.oem_props is not None and key in self.oem_props:
499 return self.oem_dicts[0][key]
500 return self.GetBuildProp(key)
501
Daniel Normand5fe8622020-01-08 17:01:11 -0800502 def GetPartitionFingerprint(self, partition):
503 return self._partition_fingerprints.get(partition, None)
504
505 def CalculatePartitionFingerprint(self, partition):
506 try:
507 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
508 except ExternalError:
509 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
510 self.GetPartitionBuildProp("ro.product.brand", partition),
511 self.GetPartitionBuildProp("ro.product.name", partition),
512 self.GetPartitionBuildProp("ro.product.device", partition),
513 self.GetPartitionBuildProp("ro.build.version.release", partition),
514 self.GetPartitionBuildProp("ro.build.id", partition),
515 self.GetPartitionBuildProp("ro.build.version.incremental", partition),
516 self.GetPartitionBuildProp("ro.build.type", partition),
517 self.GetPartitionBuildProp("ro.build.tags", partition))
518
Tao Bao1c320f82019-10-04 23:25:12 -0700519 def CalculateFingerprint(self):
520 if self.oem_props is None:
521 try:
522 return self.GetBuildProp("ro.build.fingerprint")
523 except ExternalError:
524 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
525 self.GetBuildProp("ro.product.brand"),
526 self.GetBuildProp("ro.product.name"),
527 self.GetBuildProp("ro.product.device"),
528 self.GetBuildProp("ro.build.version.release"),
529 self.GetBuildProp("ro.build.id"),
530 self.GetBuildProp("ro.build.version.incremental"),
531 self.GetBuildProp("ro.build.type"),
532 self.GetBuildProp("ro.build.tags"))
533 return "%s/%s/%s:%s" % (
534 self.GetOemProperty("ro.product.brand"),
535 self.GetOemProperty("ro.product.name"),
536 self.GetOemProperty("ro.product.device"),
537 self.GetBuildProp("ro.build.thumbprint"))
538
539 def WriteMountOemScript(self, script):
540 assert self.oem_props is not None
541 recovery_mount_options = self.info_dict.get("recovery_mount_options")
542 script.Mount("/oem", recovery_mount_options)
543
544 def WriteDeviceAssertions(self, script, oem_no_mount):
545 # Read the property directly if not using OEM properties.
546 if not self.oem_props:
547 script.AssertDevice(self.device)
548 return
549
550 # Otherwise assert OEM properties.
551 if not self.oem_dicts:
552 raise ExternalError(
553 "No OEM file provided to answer expected assertions")
554
555 for prop in self.oem_props.split():
556 values = []
557 for oem_dict in self.oem_dicts:
558 if prop in oem_dict:
559 values.append(oem_dict[prop])
560 if not values:
561 raise ExternalError(
562 "The OEM file is missing the property %s" % (prop,))
563 script.AssertOemProperty(prop, values, oem_no_mount)
564
565
Tao Bao410ad8b2018-08-24 12:08:38 -0700566def LoadInfoDict(input_file, repacking=False):
567 """Loads the key/value pairs from the given input target_files.
568
569 It reads `META/misc_info.txt` file in the target_files input, does sanity
570 checks and returns the parsed key/value pairs for to the given build. It's
571 usually called early when working on input target_files files, e.g. when
572 generating OTAs, or signing builds. Note that the function may be called
573 against an old target_files file (i.e. from past dessert releases). So the
574 property parsing needs to be backward compatible.
575
576 In a `META/misc_info.txt`, a few properties are stored as links to the files
577 in the PRODUCT_OUT directory. It works fine with the build system. However,
578 they are no longer available when (re)generating images from target_files zip.
579 When `repacking` is True, redirect these properties to the actual files in the
580 unzipped directory.
581
582 Args:
583 input_file: The input target_files file, which could be an open
584 zipfile.ZipFile instance, or a str for the dir that contains the files
585 unzipped from a target_files file.
586 repacking: Whether it's trying repack an target_files file after loading the
587 info dict (default: False). If so, it will rewrite a few loaded
588 properties (e.g. selinux_fc, root_dir) to point to the actual files in
589 target_files file. When doing repacking, `input_file` must be a dir.
590
591 Returns:
592 A dict that contains the parsed key/value pairs.
593
594 Raises:
595 AssertionError: On invalid input arguments.
596 ValueError: On malformed input values.
597 """
598 if repacking:
599 assert isinstance(input_file, str), \
600 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700601
Doug Zongkerc9253822014-02-04 12:17:58 -0800602 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700603 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800604 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800605 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700606 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800607 try:
608 with open(path) as f:
609 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700610 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800611 if e.errno == errno.ENOENT:
612 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800613
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700614 try:
Michael Runge6e836112014-04-15 17:40:21 -0700615 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700616 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700617 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700618
Tao Bao410ad8b2018-08-24 12:08:38 -0700619 if "recovery_api_version" not in d:
620 raise ValueError("Failed to find 'recovery_api_version'")
621 if "fstab_version" not in d:
622 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800623
Tao Bao410ad8b2018-08-24 12:08:38 -0700624 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700625 # "selinux_fc" properties should point to the file_contexts files
626 # (file_contexts.bin) under META/.
627 for key in d:
628 if key.endswith("selinux_fc"):
629 fc_basename = os.path.basename(d[key])
630 fc_config = os.path.join(input_file, "META", fc_basename)
631 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700632
Daniel Norman72c626f2019-05-13 15:58:14 -0700633 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700634
Tom Cherryd14b8952018-08-09 14:26:00 -0700635 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700636 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700637 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700638 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700639
David Anderson0ec64ac2019-12-06 12:21:18 -0800640 # Redirect {partition}_base_fs_file for each of the named partitions.
641 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
642 key_name = part_name + "_base_fs_file"
643 if key_name not in d:
644 continue
645 basename = os.path.basename(d[key_name])
646 base_fs_file = os.path.join(input_file, "META", basename)
647 if os.path.exists(base_fs_file):
648 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700649 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700650 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800651 "Failed to find %s base fs file: %s", part_name, base_fs_file)
652 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700653
Doug Zongker37974732010-09-16 17:44:38 -0700654 def makeint(key):
655 if key in d:
656 d[key] = int(d[key], 0)
657
658 makeint("recovery_api_version")
659 makeint("blocksize")
660 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700661 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700662 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700663 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700664 makeint("recovery_size")
665 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800666 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700667
Tao Bao765668f2019-10-04 22:03:00 -0700668 # Load recovery fstab if applicable.
669 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800670
Tianjie Xu861f4132018-09-12 11:49:33 -0700671 # Tries to load the build props for all partitions with care_map, including
672 # system and vendor.
673 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800674 partition_prop = "{}.build.prop".format(partition)
675 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700676 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800677 # Some partition might use /<partition>/etc/build.prop as the new path.
678 # TODO: try new path first when majority of them switch to the new path.
679 if not d[partition_prop]:
680 d[partition_prop] = LoadBuildProp(
681 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700682 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800683
Tao Bao3ed35d32019-10-07 20:48:48 -0700684 # Set up the salt (based on fingerprint) that will be used when adding AVB
685 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800686 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700687 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800688 for partition in PARTITIONS_WITH_CARE_MAP:
689 fingerprint = build_info.GetPartitionFingerprint(partition)
690 if fingerprint:
691 d["avb_{}_salt".format(partition)] = sha256(fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800692
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700693 return d
694
Tao Baod1de6f32017-03-01 16:38:48 -0800695
Tao Baobcd1d162017-08-26 13:10:26 -0700696def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700697 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700698 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700699 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700700 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700701 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700702 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700703
Tao Baod1de6f32017-03-01 16:38:48 -0800704
Daniel Norman4cc9df62019-07-18 10:11:07 -0700705def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900706 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700707 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900708
Daniel Norman4cc9df62019-07-18 10:11:07 -0700709
710def LoadDictionaryFromFile(file_path):
711 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900712 return LoadDictionaryFromLines(lines)
713
714
Michael Runge6e836112014-04-15 17:40:21 -0700715def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700716 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700717 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700718 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700719 if not line or line.startswith("#"):
720 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700721 if "=" in line:
722 name, value = line.split("=", 1)
723 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700724 return d
725
Tao Baod1de6f32017-03-01 16:38:48 -0800726
Tianjie Xucfa86222016-03-07 16:31:19 -0800727def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
728 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700729 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800730 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700731 self.mount_point = mount_point
732 self.fs_type = fs_type
733 self.device = device
734 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700735 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700736
737 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800738 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700739 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700740 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700741 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700742
Tao Baod1de6f32017-03-01 16:38:48 -0800743 assert fstab_version == 2
744
745 d = {}
746 for line in data.split("\n"):
747 line = line.strip()
748 if not line or line.startswith("#"):
749 continue
750
751 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
752 pieces = line.split()
753 if len(pieces) != 5:
754 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
755
756 # Ignore entries that are managed by vold.
757 options = pieces[4]
758 if "voldmanaged=" in options:
759 continue
760
761 # It's a good line, parse it.
762 length = 0
763 options = options.split(",")
764 for i in options:
765 if i.startswith("length="):
766 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800767 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800768 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700769 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800770
Tao Baod1de6f32017-03-01 16:38:48 -0800771 mount_flags = pieces[3]
772 # Honor the SELinux context if present.
773 context = None
774 for i in mount_flags.split(","):
775 if i.startswith("context="):
776 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800777
Tao Baod1de6f32017-03-01 16:38:48 -0800778 mount_point = pieces[1]
779 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
780 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800781
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700782 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700783 # system. Other areas assume system is always at "/system" so point /system
784 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700785 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800786 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700787 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700788 return d
789
790
Tao Bao765668f2019-10-04 22:03:00 -0700791def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
792 """Finds the path to recovery fstab and loads its contents."""
793 # recovery fstab is only meaningful when installing an update via recovery
794 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
795 if info_dict.get('ab_update') == 'true':
796 return None
797
798 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
799 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
800 # cases, since it may load the info_dict from an old build (e.g. when
801 # generating incremental OTAs from that build).
802 system_root_image = info_dict.get('system_root_image') == 'true'
803 if info_dict.get('no_recovery') != 'true':
804 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
805 if isinstance(input_file, zipfile.ZipFile):
806 if recovery_fstab_path not in input_file.namelist():
807 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
808 else:
809 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
810 if not os.path.exists(path):
811 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
812 return LoadRecoveryFSTab(
813 read_helper, info_dict['fstab_version'], recovery_fstab_path,
814 system_root_image)
815
816 if info_dict.get('recovery_as_boot') == 'true':
817 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
818 if isinstance(input_file, zipfile.ZipFile):
819 if recovery_fstab_path not in input_file.namelist():
820 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
821 else:
822 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
823 if not os.path.exists(path):
824 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
825 return LoadRecoveryFSTab(
826 read_helper, info_dict['fstab_version'], recovery_fstab_path,
827 system_root_image)
828
829 return None
830
831
Doug Zongker37974732010-09-16 17:44:38 -0700832def DumpInfoDict(d):
833 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700834 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700835
Dan Albert8b72aef2015-03-23 19:13:21 -0700836
Daniel Norman55417142019-11-25 16:04:36 -0800837def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700838 """Merges dynamic partition info variables.
839
840 Args:
841 framework_dict: The dictionary of dynamic partition info variables from the
842 partial framework target files.
843 vendor_dict: The dictionary of dynamic partition info variables from the
844 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700845
846 Returns:
847 The merged dynamic partition info dictionary.
848 """
849 merged_dict = {}
850 # Partition groups and group sizes are defined by the vendor dict because
851 # these values may vary for each board that uses a shared system image.
852 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800853 framework_dynamic_partition_list = framework_dict.get(
854 "dynamic_partition_list", "")
855 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
856 merged_dict["dynamic_partition_list"] = ("%s %s" % (
857 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700858 for partition_group in merged_dict["super_partition_groups"].split(" "):
859 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800860 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700861 if key not in vendor_dict:
862 raise ValueError("Vendor dict does not contain required key %s." % key)
863 merged_dict[key] = vendor_dict[key]
864
865 # Set the partition group's partition list using a concatenation of the
866 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800867 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700868 merged_dict[key] = (
869 "%s %s" %
870 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530871
872 # Pick virtual ab related flags from vendor dict, if defined.
873 if "virtual_ab" in vendor_dict.keys():
874 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
875 if "virtual_ab_retrofit" in vendor_dict.keys():
876 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700877 return merged_dict
878
879
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800880def AppendAVBSigningArgs(cmd, partition):
881 """Append signing arguments for avbtool."""
882 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
883 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700884 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
885 new_key_path = os.path.join(OPTIONS.search_path, key_path)
886 if os.path.exists(new_key_path):
887 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800888 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
889 if key_path and algorithm:
890 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700891 avb_salt = OPTIONS.info_dict.get("avb_salt")
892 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700893 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700894 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800895
896
Tao Bao765668f2019-10-04 22:03:00 -0700897def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700898 """Returns the VBMeta arguments for partition.
899
900 It sets up the VBMeta argument by including the partition descriptor from the
901 given 'image', or by configuring the partition as a chained partition.
902
903 Args:
904 partition: The name of the partition (e.g. "system").
905 image: The path to the partition image.
906 info_dict: A dict returned by common.LoadInfoDict(). Will use
907 OPTIONS.info_dict if None has been given.
908
909 Returns:
910 A list of VBMeta arguments.
911 """
912 if info_dict is None:
913 info_dict = OPTIONS.info_dict
914
915 # Check if chain partition is used.
916 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +0800917 if not key_path:
918 return ["--include_descriptors_from_image", image]
919
920 # For a non-A/B device, we don't chain /recovery nor include its descriptor
921 # into vbmeta.img. The recovery image will be configured on an independent
922 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
923 # See details at
924 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -0700925 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +0800926 return []
927
928 # Otherwise chain the partition into vbmeta.
929 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
930 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -0700931
932
Tao Bao02a08592018-07-22 12:40:45 -0700933def GetAvbChainedPartitionArg(partition, info_dict, key=None):
934 """Constructs and returns the arg to build or verify a chained partition.
935
936 Args:
937 partition: The partition name.
938 info_dict: The info dict to look up the key info and rollback index
939 location.
940 key: The key to be used for building or verifying the partition. Defaults to
941 the key listed in info_dict.
942
943 Returns:
944 A string of form "partition:rollback_index_location:key" that can be used to
945 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700946 """
947 if key is None:
948 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700949 if key and not os.path.exists(key) and OPTIONS.search_path:
950 new_key_path = os.path.join(OPTIONS.search_path, key)
951 if os.path.exists(new_key_path):
952 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700953 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700954 rollback_index_location = info_dict[
955 "avb_" + partition + "_rollback_index_location"]
956 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
957
958
Tianjie20dd8f22020-04-19 15:51:16 -0700959def ConstructAftlMakeImageCommands(output_image):
960 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -0700961
962 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -0700963 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -0700964 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
965 assert OPTIONS.aftl_manufacturer_key_path is not None, \
966 'No AFTL manufacturer key provided.'
967
968 vbmeta_image = MakeTempFile()
969 os.rename(output_image, vbmeta_image)
970 build_info = BuildInfo(OPTIONS.info_dict)
971 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -0700972 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -0700973 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -0700974 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -0700975 "--vbmeta_image_path", vbmeta_image,
976 "--output", output_image,
977 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -0700978 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -0700979 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
980 "--algorithm", "SHA256_RSA4096",
981 "--padding", "4096"]
982 if OPTIONS.aftl_signer_helper:
983 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -0700984 return aftl_cmd
985
986
987def AddAftlInclusionProof(output_image):
988 """Appends the aftl inclusion proof to the vbmeta image."""
989
990 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -0700991 RunAndCheckOutput(aftl_cmd)
992
993 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
994 output_image, '--transparency_log_pub_keys',
995 OPTIONS.aftl_key_path]
996 RunAndCheckOutput(verify_cmd)
997
998
Daniel Norman276f0622019-07-26 14:13:51 -0700999def BuildVBMeta(image_path, partitions, name, needed_partitions):
1000 """Creates a VBMeta image.
1001
1002 It generates the requested VBMeta image. The requested image could be for
1003 top-level or chained VBMeta image, which is determined based on the name.
1004
1005 Args:
1006 image_path: The output path for the new VBMeta image.
1007 partitions: A dict that's keyed by partition names with image paths as
1008 values. Only valid partition names are accepted, as listed in
1009 common.AVB_PARTITIONS.
1010 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1011 needed_partitions: Partitions whose descriptors should be included into the
1012 generated VBMeta image.
1013
1014 Raises:
1015 AssertionError: On invalid input args.
1016 """
1017 avbtool = OPTIONS.info_dict["avb_avbtool"]
1018 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1019 AppendAVBSigningArgs(cmd, name)
1020
1021 for partition, path in partitions.items():
1022 if partition not in needed_partitions:
1023 continue
1024 assert (partition in AVB_PARTITIONS or
1025 partition in AVB_VBMETA_PARTITIONS), \
1026 'Unknown partition: {}'.format(partition)
1027 assert os.path.exists(path), \
1028 'Failed to find {} for {}'.format(path, partition)
1029 cmd.extend(GetAvbPartitionArg(partition, path))
1030
1031 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1032 if args and args.strip():
1033 split_args = shlex.split(args)
1034 for index, arg in enumerate(split_args[:-1]):
1035 # Sanity check that the image file exists. Some images might be defined
1036 # as a path relative to source tree, which may not be available at the
1037 # same location when running this script (we have the input target_files
1038 # zip only). For such cases, we additionally scan other locations (e.g.
1039 # IMAGES/, RADIO/, etc) before bailing out.
1040 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001041 chained_image = split_args[index + 1]
1042 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001043 continue
1044 found = False
1045 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1046 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001047 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001048 if os.path.exists(alt_path):
1049 split_args[index + 1] = alt_path
1050 found = True
1051 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001052 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001053 cmd.extend(split_args)
1054
1055 RunAndCheckOutput(cmd)
1056
Tianjie Xueaed60c2020-03-12 00:33:28 -07001057 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001058 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001059 AddAftlInclusionProof(image_path)
1060
Daniel Norman276f0622019-07-26 14:13:51 -07001061
Steve Mucklee1b10862019-07-10 10:49:37 -07001062def _MakeRamdisk(sourcedir, fs_config_file=None):
1063 ramdisk_img = tempfile.NamedTemporaryFile()
1064
1065 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1066 cmd = ["mkbootfs", "-f", fs_config_file,
1067 os.path.join(sourcedir, "RAMDISK")]
1068 else:
1069 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1070 p1 = Run(cmd, stdout=subprocess.PIPE)
1071 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
1072
1073 p2.wait()
1074 p1.wait()
1075 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
1076 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
1077
1078 return ramdisk_img
1079
1080
Steve Muckle9793cf62020-04-08 18:27:00 -07001081def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001082 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001083 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001084
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001085 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001086 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1087 we are building a two-step special image (i.e. building a recovery image to
1088 be loaded into /boot in two-step OTAs).
1089
1090 Return the image data, or None if sourcedir does not appear to contains files
1091 for building the requested image.
1092 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001093
Steve Muckle9793cf62020-04-08 18:27:00 -07001094 # "boot" or "recovery", without extension.
1095 partition_name = os.path.basename(sourcedir).lower()
1096
1097 if partition_name == "recovery":
1098 kernel = "kernel"
1099 else:
1100 kernel = image_name.replace("boot", "kernel")
1101 kernel = kernel.replace(".img","")
1102 if not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001103 return None
1104
1105 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001106 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001107
Doug Zongkerd5131602012-08-02 14:46:42 -07001108 if info_dict is None:
1109 info_dict = OPTIONS.info_dict
1110
Doug Zongkereef39442009-04-02 12:14:19 -07001111 img = tempfile.NamedTemporaryFile()
1112
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001113 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001114 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001115
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001116 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1117 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1118
Steve Muckle9793cf62020-04-08 18:27:00 -07001119 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001120
Benoit Fradina45a8682014-07-14 21:00:43 +02001121 fn = os.path.join(sourcedir, "second")
1122 if os.access(fn, os.F_OK):
1123 cmd.append("--second")
1124 cmd.append(fn)
1125
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001126 fn = os.path.join(sourcedir, "dtb")
1127 if os.access(fn, os.F_OK):
1128 cmd.append("--dtb")
1129 cmd.append(fn)
1130
Doug Zongker171f1cd2009-06-15 22:36:37 -07001131 fn = os.path.join(sourcedir, "cmdline")
1132 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001133 cmd.append("--cmdline")
1134 cmd.append(open(fn).read().rstrip("\n"))
1135
1136 fn = os.path.join(sourcedir, "base")
1137 if os.access(fn, os.F_OK):
1138 cmd.append("--base")
1139 cmd.append(open(fn).read().rstrip("\n"))
1140
Ying Wang4de6b5b2010-08-25 14:29:34 -07001141 fn = os.path.join(sourcedir, "pagesize")
1142 if os.access(fn, os.F_OK):
1143 cmd.append("--pagesize")
1144 cmd.append(open(fn).read().rstrip("\n"))
1145
Steve Mucklef84668e2020-03-16 19:13:46 -07001146 if partition_name == "recovery":
1147 args = info_dict.get("recovery_mkbootimg_args")
1148 else:
1149 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001150 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001151 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001152
Tao Bao76def242017-11-21 09:25:31 -08001153 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001154 if args and args.strip():
1155 cmd.extend(shlex.split(args))
1156
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001157 if has_ramdisk:
1158 cmd.extend(["--ramdisk", ramdisk_img.name])
1159
Tao Baod95e9fd2015-03-29 23:07:41 -07001160 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001161 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001162 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001163 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001164 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001165 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001166
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001167 if partition_name == "recovery":
1168 if info_dict.get("include_recovery_dtbo") == "true":
1169 fn = os.path.join(sourcedir, "recovery_dtbo")
1170 cmd.extend(["--recovery_dtbo", fn])
1171 if info_dict.get("include_recovery_acpio") == "true":
1172 fn = os.path.join(sourcedir, "recovery_acpio")
1173 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001174
Tao Bao986ee862018-10-04 15:46:16 -07001175 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001176
Tao Bao76def242017-11-21 09:25:31 -08001177 if (info_dict.get("boot_signer") == "true" and
1178 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001179 # Hard-code the path as "/boot" for two-step special recovery image (which
1180 # will be loaded into /boot during the two-step OTA).
1181 if two_step_image:
1182 path = "/boot"
1183 else:
Tao Baobf70c312017-07-11 17:27:55 -07001184 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001185 cmd = [OPTIONS.boot_signer_path]
1186 cmd.extend(OPTIONS.boot_signer_args)
1187 cmd.extend([path, img.name,
1188 info_dict["verity_key"] + ".pk8",
1189 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001190 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001191
Tao Baod95e9fd2015-03-29 23:07:41 -07001192 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001193 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001194 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001195 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001196 # We have switched from the prebuilt futility binary to using the tool
1197 # (futility-host) built from the source. Override the setting in the old
1198 # TF.zip.
1199 futility = info_dict["futility"]
1200 if futility.startswith("prebuilts/"):
1201 futility = "futility-host"
1202 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001203 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001204 info_dict["vboot_key"] + ".vbprivk",
1205 info_dict["vboot_subkey"] + ".vbprivk",
1206 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001207 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001208 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001209
Tao Baof3282b42015-04-01 11:21:55 -07001210 # Clean up the temp files.
1211 img_unsigned.close()
1212 img_keyblock.close()
1213
David Zeuthen8fecb282017-12-01 16:24:01 -05001214 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001215 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001216 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001217 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001218 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001219 "--partition_size", str(part_size), "--partition_name",
1220 partition_name]
1221 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001222 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001223 if args and args.strip():
1224 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001225 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001226
1227 img.seek(os.SEEK_SET, 0)
1228 data = img.read()
1229
1230 if has_ramdisk:
1231 ramdisk_img.close()
1232 img.close()
1233
1234 return data
1235
1236
Doug Zongkerd5131602012-08-02 14:46:42 -07001237def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001238 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001239 """Return a File object with the desired bootable image.
1240
1241 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1242 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1243 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001244
Doug Zongker55d93282011-01-25 17:03:34 -08001245 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1246 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001247 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001248 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001249
1250 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1251 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001252 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001253 return File.FromLocalFile(name, prebuilt_path)
1254
Tao Bao32fcdab2018-10-12 10:30:39 -07001255 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001256
1257 if info_dict is None:
1258 info_dict = OPTIONS.info_dict
1259
1260 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001261 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1262 # for recovery.
1263 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1264 prebuilt_name != "boot.img" or
1265 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001266
Doug Zongker6f1d0312014-08-22 08:07:12 -07001267 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001268 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001269 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001270 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001271 if data:
1272 return File(name, data)
1273 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001274
Doug Zongkereef39442009-04-02 12:14:19 -07001275
Steve Mucklee1b10862019-07-10 10:49:37 -07001276def _BuildVendorBootImage(sourcedir, info_dict=None):
1277 """Build a vendor boot image from the specified sourcedir.
1278
1279 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1280 turn them into a vendor boot image.
1281
1282 Return the image data, or None if sourcedir does not appear to contains files
1283 for building the requested image.
1284 """
1285
1286 if info_dict is None:
1287 info_dict = OPTIONS.info_dict
1288
1289 img = tempfile.NamedTemporaryFile()
1290
1291 ramdisk_img = _MakeRamdisk(sourcedir)
1292
1293 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1294 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1295
1296 cmd = [mkbootimg]
1297
1298 fn = os.path.join(sourcedir, "dtb")
1299 if os.access(fn, os.F_OK):
1300 cmd.append("--dtb")
1301 cmd.append(fn)
1302
1303 fn = os.path.join(sourcedir, "vendor_cmdline")
1304 if os.access(fn, os.F_OK):
1305 cmd.append("--vendor_cmdline")
1306 cmd.append(open(fn).read().rstrip("\n"))
1307
1308 fn = os.path.join(sourcedir, "base")
1309 if os.access(fn, os.F_OK):
1310 cmd.append("--base")
1311 cmd.append(open(fn).read().rstrip("\n"))
1312
1313 fn = os.path.join(sourcedir, "pagesize")
1314 if os.access(fn, os.F_OK):
1315 cmd.append("--pagesize")
1316 cmd.append(open(fn).read().rstrip("\n"))
1317
1318 args = info_dict.get("mkbootimg_args")
1319 if args and args.strip():
1320 cmd.extend(shlex.split(args))
1321
1322 args = info_dict.get("mkbootimg_version_args")
1323 if args and args.strip():
1324 cmd.extend(shlex.split(args))
1325
1326 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1327 cmd.extend(["--vendor_boot", img.name])
1328
1329 RunAndCheckOutput(cmd)
1330
1331 # AVB: if enabled, calculate and add hash.
1332 if info_dict.get("avb_enable") == "true":
1333 avbtool = info_dict["avb_avbtool"]
1334 part_size = info_dict["vendor_boot_size"]
1335 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001336 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001337 AppendAVBSigningArgs(cmd, "vendor_boot")
1338 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1339 if args and args.strip():
1340 cmd.extend(shlex.split(args))
1341 RunAndCheckOutput(cmd)
1342
1343 img.seek(os.SEEK_SET, 0)
1344 data = img.read()
1345
1346 ramdisk_img.close()
1347 img.close()
1348
1349 return data
1350
1351
1352def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1353 info_dict=None):
1354 """Return a File object with the desired vendor boot image.
1355
1356 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1357 the source files in 'unpack_dir'/'tree_subdir'."""
1358
1359 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1360 if os.path.exists(prebuilt_path):
1361 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1362 return File.FromLocalFile(name, prebuilt_path)
1363
1364 logger.info("building image from target_files %s...", tree_subdir)
1365
1366 if info_dict is None:
1367 info_dict = OPTIONS.info_dict
1368
1369 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1370 if data:
1371 return File(name, data)
1372 return None
1373
1374
Narayan Kamatha07bf042017-08-14 14:49:21 +01001375def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001376 """Gunzips the given gzip compressed file to a given output file."""
1377 with gzip.open(in_filename, "rb") as in_file, \
1378 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001379 shutil.copyfileobj(in_file, out_file)
1380
1381
Tao Bao0ff15de2019-03-20 11:26:06 -07001382def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001383 """Unzips the archive to the given directory.
1384
1385 Args:
1386 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001387 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001388 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1389 archvie. Non-matching patterns will be filtered out. If there's no match
1390 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001391 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001392 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001393 if patterns is not None:
1394 # Filter out non-matching patterns. unzip will complain otherwise.
1395 with zipfile.ZipFile(filename) as input_zip:
1396 names = input_zip.namelist()
1397 filtered = [
1398 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1399
1400 # There isn't any matching files. Don't unzip anything.
1401 if not filtered:
1402 return
1403 cmd.extend(filtered)
1404
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001405 RunAndCheckOutput(cmd)
1406
1407
Doug Zongker75f17362009-12-08 13:46:44 -08001408def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001409 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001410
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001411 Args:
1412 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1413 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1414
1415 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1416 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001417
Tao Bao1c830bf2017-12-25 10:43:47 -08001418 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001419 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001420 """
Doug Zongkereef39442009-04-02 12:14:19 -07001421
Tao Bao1c830bf2017-12-25 10:43:47 -08001422 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001423 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1424 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001425 UnzipToDir(m.group(1), tmp, pattern)
1426 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001427 filename = m.group(1)
1428 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001429 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001430
Tao Baodba59ee2018-01-09 13:21:02 -08001431 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001432
1433
Yifan Hong8a66a712019-04-04 15:37:57 -07001434def GetUserImage(which, tmpdir, input_zip,
1435 info_dict=None,
1436 allow_shared_blocks=None,
1437 hashtree_info_generator=None,
1438 reset_file_map=False):
1439 """Returns an Image object suitable for passing to BlockImageDiff.
1440
1441 This function loads the specified image from the given path. If the specified
1442 image is sparse, it also performs additional processing for OTA purpose. For
1443 example, it always adds block 0 to clobbered blocks list. It also detects
1444 files that cannot be reconstructed from the block list, for whom we should
1445 avoid applying imgdiff.
1446
1447 Args:
1448 which: The partition name.
1449 tmpdir: The directory that contains the prebuilt image and block map file.
1450 input_zip: The target-files ZIP archive.
1451 info_dict: The dict to be looked up for relevant info.
1452 allow_shared_blocks: If image is sparse, whether having shared blocks is
1453 allowed. If none, it is looked up from info_dict.
1454 hashtree_info_generator: If present and image is sparse, generates the
1455 hashtree_info for this sparse image.
1456 reset_file_map: If true and image is sparse, reset file map before returning
1457 the image.
1458 Returns:
1459 A Image object. If it is a sparse image and reset_file_map is False, the
1460 image will have file_map info loaded.
1461 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001462 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001463 info_dict = LoadInfoDict(input_zip)
1464
1465 is_sparse = info_dict.get("extfs_sparse_flag")
1466
1467 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1468 # shared blocks (i.e. some blocks will show up in multiple files' block
1469 # list). We can only allocate such shared blocks to the first "owner", and
1470 # disable imgdiff for all later occurrences.
1471 if allow_shared_blocks is None:
1472 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1473
1474 if is_sparse:
1475 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1476 hashtree_info_generator)
1477 if reset_file_map:
1478 img.ResetFileMap()
1479 return img
1480 else:
1481 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1482
1483
1484def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1485 """Returns a Image object suitable for passing to BlockImageDiff.
1486
1487 This function loads the specified non-sparse image from the given path.
1488
1489 Args:
1490 which: The partition name.
1491 tmpdir: The directory that contains the prebuilt image and block map file.
1492 Returns:
1493 A Image object.
1494 """
1495 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1496 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1497
1498 # The image and map files must have been created prior to calling
1499 # ota_from_target_files.py (since LMP).
1500 assert os.path.exists(path) and os.path.exists(mappath)
1501
Tianjie Xu41976c72019-07-03 13:57:01 -07001502 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1503
Yifan Hong8a66a712019-04-04 15:37:57 -07001504
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001505def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1506 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001507 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1508
1509 This function loads the specified sparse image from the given path, and
1510 performs additional processing for OTA purpose. For example, it always adds
1511 block 0 to clobbered blocks list. It also detects files that cannot be
1512 reconstructed from the block list, for whom we should avoid applying imgdiff.
1513
1514 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001515 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001516 tmpdir: The directory that contains the prebuilt image and block map file.
1517 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001518 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001519 hashtree_info_generator: If present, generates the hashtree_info for this
1520 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001521 Returns:
1522 A SparseImage object, with file_map info loaded.
1523 """
Tao Baoc765cca2018-01-31 17:32:40 -08001524 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1525 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1526
1527 # The image and map files must have been created prior to calling
1528 # ota_from_target_files.py (since LMP).
1529 assert os.path.exists(path) and os.path.exists(mappath)
1530
1531 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1532 # it to clobbered_blocks so that it will be written to the target
1533 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1534 clobbered_blocks = "0"
1535
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001536 image = sparse_img.SparseImage(
1537 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1538 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001539
1540 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1541 # if they contain all zeros. We can't reconstruct such a file from its block
1542 # list. Tag such entries accordingly. (Bug: 65213616)
1543 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001544 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001545 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001546 continue
1547
Tom Cherryd14b8952018-08-09 14:26:00 -07001548 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1549 # filename listed in system.map may contain an additional leading slash
1550 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1551 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001552 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001553
Tom Cherryd14b8952018-08-09 14:26:00 -07001554 # Special handling another case, where files not under /system
1555 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001556 if which == 'system' and not arcname.startswith('SYSTEM'):
1557 arcname = 'ROOT/' + arcname
1558
1559 assert arcname in input_zip.namelist(), \
1560 "Failed to find the ZIP entry for {}".format(entry)
1561
Tao Baoc765cca2018-01-31 17:32:40 -08001562 info = input_zip.getinfo(arcname)
1563 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001564
1565 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001566 # image, check the original block list to determine its completeness. Note
1567 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001568 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001569 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001570
Tao Baoc765cca2018-01-31 17:32:40 -08001571 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1572 ranges.extra['incomplete'] = True
1573
1574 return image
1575
1576
Doug Zongkereef39442009-04-02 12:14:19 -07001577def GetKeyPasswords(keylist):
1578 """Given a list of keys, prompt the user to enter passwords for
1579 those which require them. Return a {key: password} dict. password
1580 will be None if the key has no password."""
1581
Doug Zongker8ce7c252009-05-22 13:34:54 -07001582 no_passwords = []
1583 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001584 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001585 devnull = open("/dev/null", "w+b")
1586 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001587 # We don't need a password for things that aren't really keys.
1588 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001589 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001590 continue
1591
T.R. Fullhart37e10522013-03-18 10:31:26 -07001592 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001593 "-inform", "DER", "-nocrypt"],
1594 stdin=devnull.fileno(),
1595 stdout=devnull.fileno(),
1596 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001597 p.communicate()
1598 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001599 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001600 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001601 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001602 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1603 "-inform", "DER", "-passin", "pass:"],
1604 stdin=devnull.fileno(),
1605 stdout=devnull.fileno(),
1606 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001607 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001608 if p.returncode == 0:
1609 # Encrypted key with empty string as password.
1610 key_passwords[k] = ''
1611 elif stderr.startswith('Error decrypting key'):
1612 # Definitely encrypted key.
1613 # It would have said "Error reading key" if it didn't parse correctly.
1614 need_passwords.append(k)
1615 else:
1616 # Potentially, a type of key that openssl doesn't understand.
1617 # We'll let the routines in signapk.jar handle it.
1618 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001619 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001620
T.R. Fullhart37e10522013-03-18 10:31:26 -07001621 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001622 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001623 return key_passwords
1624
1625
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001626def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001627 """Gets the minSdkVersion declared in the APK.
1628
changho.shin0f125362019-07-08 10:59:00 +09001629 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001630 This can be both a decimal number (API Level) or a codename.
1631
1632 Args:
1633 apk_name: The APK filename.
1634
1635 Returns:
1636 The parsed SDK version string.
1637
1638 Raises:
1639 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001640 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001641 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001642 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001643 stderr=subprocess.PIPE)
1644 stdoutdata, stderrdata = proc.communicate()
1645 if proc.returncode != 0:
1646 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001647 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001648 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001649
Tao Baof47bf0f2018-03-21 23:28:51 -07001650 for line in stdoutdata.split("\n"):
1651 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001652 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1653 if m:
1654 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001655 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001656
1657
1658def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001659 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001660
Tao Baof47bf0f2018-03-21 23:28:51 -07001661 If minSdkVersion is set to a codename, it is translated to a number using the
1662 provided map.
1663
1664 Args:
1665 apk_name: The APK filename.
1666
1667 Returns:
1668 The parsed SDK version number.
1669
1670 Raises:
1671 ExternalError: On failing to get the min SDK version number.
1672 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001673 version = GetMinSdkVersion(apk_name)
1674 try:
1675 return int(version)
1676 except ValueError:
1677 # Not a decimal number. Codename?
1678 if version in codename_to_api_level_map:
1679 return codename_to_api_level_map[version]
1680 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001681 raise ExternalError(
1682 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1683 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001684
1685
1686def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001687 codename_to_api_level_map=None, whole_file=False,
1688 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001689 """Sign the input_name zip/jar/apk, producing output_name. Use the
1690 given key and password (the latter may be None if the key does not
1691 have a password.
1692
Doug Zongker951495f2009-08-14 12:44:19 -07001693 If whole_file is true, use the "-w" option to SignApk to embed a
1694 signature that covers the whole file in the archive comment of the
1695 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001696
1697 min_api_level is the API Level (int) of the oldest platform this file may end
1698 up on. If not specified for an APK, the API Level is obtained by interpreting
1699 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1700
1701 codename_to_api_level_map is needed to translate the codename which may be
1702 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001703
1704 Caller may optionally specify extra args to be passed to SignApk, which
1705 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001706 """
Tao Bao76def242017-11-21 09:25:31 -08001707 if codename_to_api_level_map is None:
1708 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001709 if extra_signapk_args is None:
1710 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001711
Alex Klyubin9667b182015-12-10 13:38:50 -08001712 java_library_path = os.path.join(
1713 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1714
Tao Baoe95540e2016-11-08 12:08:53 -08001715 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1716 ["-Djava.library.path=" + java_library_path,
1717 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001718 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001719 if whole_file:
1720 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001721
1722 min_sdk_version = min_api_level
1723 if min_sdk_version is None:
1724 if not whole_file:
1725 min_sdk_version = GetMinSdkVersionInt(
1726 input_name, codename_to_api_level_map)
1727 if min_sdk_version is not None:
1728 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1729
T.R. Fullhart37e10522013-03-18 10:31:26 -07001730 cmd.extend([key + OPTIONS.public_key_suffix,
1731 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001732 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001733
Tao Bao73dd4f42018-10-04 16:25:33 -07001734 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001735 if password is not None:
1736 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001737 stdoutdata, _ = proc.communicate(password)
1738 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001739 raise ExternalError(
1740 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001741 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001742
Doug Zongkereef39442009-04-02 12:14:19 -07001743
Doug Zongker37974732010-09-16 17:44:38 -07001744def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001745 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001746
Tao Bao9dd909e2017-11-14 11:27:32 -08001747 For non-AVB images, raise exception if the data is too big. Print a warning
1748 if the data is nearing the maximum size.
1749
1750 For AVB images, the actual image size should be identical to the limit.
1751
1752 Args:
1753 data: A string that contains all the data for the partition.
1754 target: The partition name. The ".img" suffix is optional.
1755 info_dict: The dict to be looked up for relevant info.
1756 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001757 if target.endswith(".img"):
1758 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001759 mount_point = "/" + target
1760
Ying Wangf8824af2014-06-03 14:07:27 -07001761 fs_type = None
1762 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001763 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001764 if mount_point == "/userdata":
1765 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001766 p = info_dict["fstab"][mount_point]
1767 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001768 device = p.device
1769 if "/" in device:
1770 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001771 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001772 if not fs_type or not limit:
1773 return
Doug Zongkereef39442009-04-02 12:14:19 -07001774
Andrew Boie0f9aec82012-02-14 09:32:52 -08001775 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001776 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1777 # path.
1778 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1779 if size != limit:
1780 raise ExternalError(
1781 "Mismatching image size for %s: expected %d actual %d" % (
1782 target, limit, size))
1783 else:
1784 pct = float(size) * 100.0 / limit
1785 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1786 if pct >= 99.0:
1787 raise ExternalError(msg)
1788 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001789 logger.warning("\n WARNING: %s\n", msg)
1790 else:
1791 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001792
1793
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001794def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001795 """Parses the APK certs info from a given target-files zip.
1796
1797 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1798 tuple with the following elements: (1) a dictionary that maps packages to
1799 certs (based on the "certificate" and "private_key" attributes in the file;
1800 (2) a string representing the extension of compressed APKs in the target files
1801 (e.g ".gz", ".bro").
1802
1803 Args:
1804 tf_zip: The input target_files ZipFile (already open).
1805
1806 Returns:
1807 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1808 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1809 no compressed APKs.
1810 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001811 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001812 compressed_extension = None
1813
Tao Bao0f990332017-09-08 19:02:54 -07001814 # META/apkcerts.txt contains the info for _all_ the packages known at build
1815 # time. Filter out the ones that are not installed.
1816 installed_files = set()
1817 for name in tf_zip.namelist():
1818 basename = os.path.basename(name)
1819 if basename:
1820 installed_files.add(basename)
1821
Tao Baoda30cfa2017-12-01 16:19:46 -08001822 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001823 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001824 if not line:
1825 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001826 m = re.match(
1827 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001828 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1829 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001830 line)
1831 if not m:
1832 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001833
Tao Bao818ddf52018-01-05 11:17:34 -08001834 matches = m.groupdict()
1835 cert = matches["CERT"]
1836 privkey = matches["PRIVKEY"]
1837 name = matches["NAME"]
1838 this_compressed_extension = matches["COMPRESSED"]
1839
1840 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1841 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1842 if cert in SPECIAL_CERT_STRINGS and not privkey:
1843 certmap[name] = cert
1844 elif (cert.endswith(OPTIONS.public_key_suffix) and
1845 privkey.endswith(OPTIONS.private_key_suffix) and
1846 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1847 certmap[name] = cert[:-public_key_suffix_len]
1848 else:
1849 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1850
1851 if not this_compressed_extension:
1852 continue
1853
1854 # Only count the installed files.
1855 filename = name + '.' + this_compressed_extension
1856 if filename not in installed_files:
1857 continue
1858
1859 # Make sure that all the values in the compression map have the same
1860 # extension. We don't support multiple compression methods in the same
1861 # system image.
1862 if compressed_extension:
1863 if this_compressed_extension != compressed_extension:
1864 raise ValueError(
1865 "Multiple compressed extensions: {} vs {}".format(
1866 compressed_extension, this_compressed_extension))
1867 else:
1868 compressed_extension = this_compressed_extension
1869
1870 return (certmap,
1871 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001872
1873
Doug Zongkereef39442009-04-02 12:14:19 -07001874COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001875Global options
1876
1877 -p (--path) <dir>
1878 Prepend <dir>/bin to the list of places to search for binaries run by this
1879 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001880
Doug Zongker05d3dea2009-06-22 11:32:31 -07001881 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001882 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001883
Tao Bao30df8b42018-04-23 15:32:53 -07001884 -x (--extra) <key=value>
1885 Add a key/value pair to the 'extras' dict, which device-specific extension
1886 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001887
Doug Zongkereef39442009-04-02 12:14:19 -07001888 -v (--verbose)
1889 Show command lines being executed.
1890
1891 -h (--help)
1892 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001893
1894 --logfile <file>
1895 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001896"""
1897
1898def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001899 print(docstring.rstrip("\n"))
1900 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001901
1902
1903def ParseOptions(argv,
1904 docstring,
1905 extra_opts="", extra_long_opts=(),
1906 extra_option_handler=None):
1907 """Parse the options in argv and return any arguments that aren't
1908 flags. docstring is the calling module's docstring, to be displayed
1909 for errors and -h. extra_opts and extra_long_opts are for flags
1910 defined by the caller, which are processed by passing them to
1911 extra_option_handler."""
1912
1913 try:
1914 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001915 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001916 ["help", "verbose", "path=", "signapk_path=",
1917 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08001918 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001919 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1920 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07001921 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
1922 "aftl_key_path=", "aftl_manufacturer_key_path=",
1923 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001924 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001925 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001926 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001927 sys.exit(2)
1928
Doug Zongkereef39442009-04-02 12:14:19 -07001929 for o, a in opts:
1930 if o in ("-h", "--help"):
1931 Usage(docstring)
1932 sys.exit()
1933 elif o in ("-v", "--verbose"):
1934 OPTIONS.verbose = True
1935 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001936 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001937 elif o in ("--signapk_path",):
1938 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001939 elif o in ("--signapk_shared_library_path",):
1940 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001941 elif o in ("--extra_signapk_args",):
1942 OPTIONS.extra_signapk_args = shlex.split(a)
1943 elif o in ("--java_path",):
1944 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001945 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001946 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08001947 elif o in ("--android_jar_path",):
1948 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001949 elif o in ("--public_key_suffix",):
1950 OPTIONS.public_key_suffix = a
1951 elif o in ("--private_key_suffix",):
1952 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001953 elif o in ("--boot_signer_path",):
1954 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001955 elif o in ("--boot_signer_args",):
1956 OPTIONS.boot_signer_args = shlex.split(a)
1957 elif o in ("--verity_signer_path",):
1958 OPTIONS.verity_signer_path = a
1959 elif o in ("--verity_signer_args",):
1960 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07001961 elif o in ("--aftl_tool_path",):
1962 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08001963 elif o in ("--aftl_server",):
1964 OPTIONS.aftl_server = a
1965 elif o in ("--aftl_key_path",):
1966 OPTIONS.aftl_key_path = a
1967 elif o in ("--aftl_manufacturer_key_path",):
1968 OPTIONS.aftl_manufacturer_key_path = a
1969 elif o in ("--aftl_signer_helper",):
1970 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07001971 elif o in ("-s", "--device_specific"):
1972 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001973 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001974 key, value = a.split("=", 1)
1975 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001976 elif o in ("--logfile",):
1977 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001978 else:
1979 if extra_option_handler is None or not extra_option_handler(o, a):
1980 assert False, "unknown option \"%s\"" % (o,)
1981
Doug Zongker85448772014-09-09 14:59:20 -07001982 if OPTIONS.search_path:
1983 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1984 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001985
1986 return args
1987
1988
Tao Bao4c851b12016-09-19 13:54:38 -07001989def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001990 """Make a temp file and add it to the list of things to be deleted
1991 when Cleanup() is called. Return the filename."""
1992 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1993 os.close(fd)
1994 OPTIONS.tempfiles.append(fn)
1995 return fn
1996
1997
Tao Bao1c830bf2017-12-25 10:43:47 -08001998def MakeTempDir(prefix='tmp', suffix=''):
1999 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2000
2001 Returns:
2002 The absolute pathname of the new directory.
2003 """
2004 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2005 OPTIONS.tempfiles.append(dir_name)
2006 return dir_name
2007
2008
Doug Zongkereef39442009-04-02 12:14:19 -07002009def Cleanup():
2010 for i in OPTIONS.tempfiles:
2011 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002012 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002013 else:
2014 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002015 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002016
2017
2018class PasswordManager(object):
2019 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002020 self.editor = os.getenv("EDITOR")
2021 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002022
2023 def GetPasswords(self, items):
2024 """Get passwords corresponding to each string in 'items',
2025 returning a dict. (The dict may have keys in addition to the
2026 values in 'items'.)
2027
2028 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2029 user edit that file to add more needed passwords. If no editor is
2030 available, or $ANDROID_PW_FILE isn't define, prompts the user
2031 interactively in the ordinary way.
2032 """
2033
2034 current = self.ReadFile()
2035
2036 first = True
2037 while True:
2038 missing = []
2039 for i in items:
2040 if i not in current or not current[i]:
2041 missing.append(i)
2042 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002043 if not missing:
2044 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002045
2046 for i in missing:
2047 current[i] = ""
2048
2049 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002050 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002051 if sys.version_info[0] >= 3:
2052 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002053 answer = raw_input("try to edit again? [y]> ").strip()
2054 if answer and answer[0] not in 'yY':
2055 raise RuntimeError("key passwords unavailable")
2056 first = False
2057
2058 current = self.UpdateAndReadFile(current)
2059
Dan Albert8b72aef2015-03-23 19:13:21 -07002060 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002061 """Prompt the user to enter a value (password) for each key in
2062 'current' whose value is fales. Returns a new dict with all the
2063 values.
2064 """
2065 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002066 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002067 if v:
2068 result[k] = v
2069 else:
2070 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002071 result[k] = getpass.getpass(
2072 "Enter password for %s key> " % k).strip()
2073 if result[k]:
2074 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002075 return result
2076
2077 def UpdateAndReadFile(self, current):
2078 if not self.editor or not self.pwfile:
2079 return self.PromptResult(current)
2080
2081 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002082 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002083 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2084 f.write("# (Additional spaces are harmless.)\n\n")
2085
2086 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002087 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002088 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002089 f.write("[[[ %s ]]] %s\n" % (v, k))
2090 if not v and first_line is None:
2091 # position cursor on first line with no password.
2092 first_line = i + 4
2093 f.close()
2094
Tao Bao986ee862018-10-04 15:46:16 -07002095 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002096
2097 return self.ReadFile()
2098
2099 def ReadFile(self):
2100 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002101 if self.pwfile is None:
2102 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002103 try:
2104 f = open(self.pwfile, "r")
2105 for line in f:
2106 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002107 if not line or line[0] == '#':
2108 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002109 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2110 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002111 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002112 else:
2113 result[m.group(2)] = m.group(1)
2114 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002115 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002116 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002117 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002118 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002119
2120
Dan Albert8e0178d2015-01-27 15:53:15 -08002121def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2122 compress_type=None):
2123 import datetime
2124
2125 # http://b/18015246
2126 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2127 # for files larger than 2GiB. We can work around this by adjusting their
2128 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2129 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2130 # it isn't clear to me exactly what circumstances cause this).
2131 # `zipfile.write()` must be used directly to work around this.
2132 #
2133 # This mess can be avoided if we port to python3.
2134 saved_zip64_limit = zipfile.ZIP64_LIMIT
2135 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2136
2137 if compress_type is None:
2138 compress_type = zip_file.compression
2139 if arcname is None:
2140 arcname = filename
2141
2142 saved_stat = os.stat(filename)
2143
2144 try:
2145 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2146 # file to be zipped and reset it when we're done.
2147 os.chmod(filename, perms)
2148
2149 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002150 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2151 # intentional. zip stores datetimes in local time without a time zone
2152 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2153 # in the zip archive.
2154 local_epoch = datetime.datetime.fromtimestamp(0)
2155 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002156 os.utime(filename, (timestamp, timestamp))
2157
2158 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2159 finally:
2160 os.chmod(filename, saved_stat.st_mode)
2161 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2162 zipfile.ZIP64_LIMIT = saved_zip64_limit
2163
2164
Tao Bao58c1b962015-05-20 09:32:18 -07002165def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002166 compress_type=None):
2167 """Wrap zipfile.writestr() function to work around the zip64 limit.
2168
2169 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2170 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2171 when calling crc32(bytes).
2172
2173 But it still works fine to write a shorter string into a large zip file.
2174 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2175 when we know the string won't be too long.
2176 """
2177
2178 saved_zip64_limit = zipfile.ZIP64_LIMIT
2179 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2180
2181 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2182 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002183 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002184 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002185 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002186 else:
Tao Baof3282b42015-04-01 11:21:55 -07002187 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002188 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2189 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2190 # such a case (since
2191 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2192 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2193 # permission bits. We follow the logic in Python 3 to get consistent
2194 # behavior between using the two versions.
2195 if not zinfo.external_attr:
2196 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002197
2198 # If compress_type is given, it overrides the value in zinfo.
2199 if compress_type is not None:
2200 zinfo.compress_type = compress_type
2201
Tao Bao58c1b962015-05-20 09:32:18 -07002202 # If perms is given, it has a priority.
2203 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002204 # If perms doesn't set the file type, mark it as a regular file.
2205 if perms & 0o770000 == 0:
2206 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002207 zinfo.external_attr = perms << 16
2208
Tao Baof3282b42015-04-01 11:21:55 -07002209 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002210 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2211
Dan Albert8b72aef2015-03-23 19:13:21 -07002212 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002213 zipfile.ZIP64_LIMIT = saved_zip64_limit
2214
2215
Tao Bao89d7ab22017-12-14 17:05:33 -08002216def ZipDelete(zip_filename, entries):
2217 """Deletes entries from a ZIP file.
2218
2219 Since deleting entries from a ZIP file is not supported, it shells out to
2220 'zip -d'.
2221
2222 Args:
2223 zip_filename: The name of the ZIP file.
2224 entries: The name of the entry, or the list of names to be deleted.
2225
2226 Raises:
2227 AssertionError: In case of non-zero return from 'zip'.
2228 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002229 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002230 entries = [entries]
2231 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002232 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002233
2234
Tao Baof3282b42015-04-01 11:21:55 -07002235def ZipClose(zip_file):
2236 # http://b/18015246
2237 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2238 # central directory.
2239 saved_zip64_limit = zipfile.ZIP64_LIMIT
2240 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2241
2242 zip_file.close()
2243
2244 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002245
2246
2247class DeviceSpecificParams(object):
2248 module = None
2249 def __init__(self, **kwargs):
2250 """Keyword arguments to the constructor become attributes of this
2251 object, which is passed to all functions in the device-specific
2252 module."""
Tao Bao38884282019-07-10 22:20:56 -07002253 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002254 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002255 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002256
2257 if self.module is None:
2258 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002259 if not path:
2260 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002261 try:
2262 if os.path.isdir(path):
2263 info = imp.find_module("releasetools", [path])
2264 else:
2265 d, f = os.path.split(path)
2266 b, x = os.path.splitext(f)
2267 if x == ".py":
2268 f = b
2269 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002270 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002271 self.module = imp.load_module("device_specific", *info)
2272 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002273 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002274
2275 def _DoCall(self, function_name, *args, **kwargs):
2276 """Call the named function in the device-specific module, passing
2277 the given args and kwargs. The first argument to the call will be
2278 the DeviceSpecific object itself. If there is no module, or the
2279 module does not define the function, return the value of the
2280 'default' kwarg (which itself defaults to None)."""
2281 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002282 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002283 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2284
2285 def FullOTA_Assertions(self):
2286 """Called after emitting the block of assertions at the top of a
2287 full OTA package. Implementations can add whatever additional
2288 assertions they like."""
2289 return self._DoCall("FullOTA_Assertions")
2290
Doug Zongkere5ff5902012-01-17 10:55:37 -08002291 def FullOTA_InstallBegin(self):
2292 """Called at the start of full OTA installation."""
2293 return self._DoCall("FullOTA_InstallBegin")
2294
Yifan Hong10c530d2018-12-27 17:34:18 -08002295 def FullOTA_GetBlockDifferences(self):
2296 """Called during full OTA installation and verification.
2297 Implementation should return a list of BlockDifference objects describing
2298 the update on each additional partitions.
2299 """
2300 return self._DoCall("FullOTA_GetBlockDifferences")
2301
Doug Zongker05d3dea2009-06-22 11:32:31 -07002302 def FullOTA_InstallEnd(self):
2303 """Called at the end of full OTA installation; typically this is
2304 used to install the image for the device's baseband processor."""
2305 return self._DoCall("FullOTA_InstallEnd")
2306
2307 def IncrementalOTA_Assertions(self):
2308 """Called after emitting the block of assertions at the top of an
2309 incremental OTA package. Implementations can add whatever
2310 additional assertions they like."""
2311 return self._DoCall("IncrementalOTA_Assertions")
2312
Doug Zongkere5ff5902012-01-17 10:55:37 -08002313 def IncrementalOTA_VerifyBegin(self):
2314 """Called at the start of the verification phase of incremental
2315 OTA installation; additional checks can be placed here to abort
2316 the script before any changes are made."""
2317 return self._DoCall("IncrementalOTA_VerifyBegin")
2318
Doug Zongker05d3dea2009-06-22 11:32:31 -07002319 def IncrementalOTA_VerifyEnd(self):
2320 """Called at the end of the verification phase of incremental OTA
2321 installation; additional checks can be placed here to abort the
2322 script before any changes are made."""
2323 return self._DoCall("IncrementalOTA_VerifyEnd")
2324
Doug Zongkere5ff5902012-01-17 10:55:37 -08002325 def IncrementalOTA_InstallBegin(self):
2326 """Called at the start of incremental OTA installation (after
2327 verification is complete)."""
2328 return self._DoCall("IncrementalOTA_InstallBegin")
2329
Yifan Hong10c530d2018-12-27 17:34:18 -08002330 def IncrementalOTA_GetBlockDifferences(self):
2331 """Called during incremental OTA installation and verification.
2332 Implementation should return a list of BlockDifference objects describing
2333 the update on each additional partitions.
2334 """
2335 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2336
Doug Zongker05d3dea2009-06-22 11:32:31 -07002337 def IncrementalOTA_InstallEnd(self):
2338 """Called at the end of incremental OTA installation; typically
2339 this is used to install the image for the device's baseband
2340 processor."""
2341 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002342
Tao Bao9bc6bb22015-11-09 16:58:28 -08002343 def VerifyOTA_Assertions(self):
2344 return self._DoCall("VerifyOTA_Assertions")
2345
Tao Bao76def242017-11-21 09:25:31 -08002346
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002347class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002348 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002349 self.name = name
2350 self.data = data
2351 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002352 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002353 self.sha1 = sha1(data).hexdigest()
2354
2355 @classmethod
2356 def FromLocalFile(cls, name, diskname):
2357 f = open(diskname, "rb")
2358 data = f.read()
2359 f.close()
2360 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002361
2362 def WriteToTemp(self):
2363 t = tempfile.NamedTemporaryFile()
2364 t.write(self.data)
2365 t.flush()
2366 return t
2367
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002368 def WriteToDir(self, d):
2369 with open(os.path.join(d, self.name), "wb") as fp:
2370 fp.write(self.data)
2371
Geremy Condra36bd3652014-02-06 19:45:10 -08002372 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002373 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002374
Tao Bao76def242017-11-21 09:25:31 -08002375
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002376DIFF_PROGRAM_BY_EXT = {
2377 ".gz" : "imgdiff",
2378 ".zip" : ["imgdiff", "-z"],
2379 ".jar" : ["imgdiff", "-z"],
2380 ".apk" : ["imgdiff", "-z"],
2381 ".img" : "imgdiff",
2382 }
2383
Tao Bao76def242017-11-21 09:25:31 -08002384
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002385class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002386 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002387 self.tf = tf
2388 self.sf = sf
2389 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002390 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002391
2392 def ComputePatch(self):
2393 """Compute the patch (as a string of data) needed to turn sf into
2394 tf. Returns the same tuple as GetPatch()."""
2395
2396 tf = self.tf
2397 sf = self.sf
2398
Doug Zongker24cd2802012-08-14 16:36:15 -07002399 if self.diff_program:
2400 diff_program = self.diff_program
2401 else:
2402 ext = os.path.splitext(tf.name)[1]
2403 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002404
2405 ttemp = tf.WriteToTemp()
2406 stemp = sf.WriteToTemp()
2407
2408 ext = os.path.splitext(tf.name)[1]
2409
2410 try:
2411 ptemp = tempfile.NamedTemporaryFile()
2412 if isinstance(diff_program, list):
2413 cmd = copy.copy(diff_program)
2414 else:
2415 cmd = [diff_program]
2416 cmd.append(stemp.name)
2417 cmd.append(ttemp.name)
2418 cmd.append(ptemp.name)
2419 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002420 err = []
2421 def run():
2422 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002423 if e:
2424 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002425 th = threading.Thread(target=run)
2426 th.start()
2427 th.join(timeout=300) # 5 mins
2428 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002429 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002430 p.terminate()
2431 th.join(5)
2432 if th.is_alive():
2433 p.kill()
2434 th.join()
2435
Tianjie Xua2a9f992018-01-05 15:15:54 -08002436 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002437 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002438 self.patch = None
2439 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002440 diff = ptemp.read()
2441 finally:
2442 ptemp.close()
2443 stemp.close()
2444 ttemp.close()
2445
2446 self.patch = diff
2447 return self.tf, self.sf, self.patch
2448
2449
2450 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002451 """Returns a tuple of (target_file, source_file, patch_data).
2452
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002453 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002454 computing the patch failed.
2455 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002456 return self.tf, self.sf, self.patch
2457
2458
2459def ComputeDifferences(diffs):
2460 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002461 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002462
2463 # Do the largest files first, to try and reduce the long-pole effect.
2464 by_size = [(i.tf.size, i) for i in diffs]
2465 by_size.sort(reverse=True)
2466 by_size = [i[1] for i in by_size]
2467
2468 lock = threading.Lock()
2469 diff_iter = iter(by_size) # accessed under lock
2470
2471 def worker():
2472 try:
2473 lock.acquire()
2474 for d in diff_iter:
2475 lock.release()
2476 start = time.time()
2477 d.ComputePatch()
2478 dur = time.time() - start
2479 lock.acquire()
2480
2481 tf, sf, patch = d.GetPatch()
2482 if sf.name == tf.name:
2483 name = tf.name
2484 else:
2485 name = "%s (%s)" % (tf.name, sf.name)
2486 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002487 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002488 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002489 logger.info(
2490 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2491 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002492 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002493 except Exception:
2494 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002495 raise
2496
2497 # start worker threads; wait for them all to finish.
2498 threads = [threading.Thread(target=worker)
2499 for i in range(OPTIONS.worker_threads)]
2500 for th in threads:
2501 th.start()
2502 while threads:
2503 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002504
2505
Dan Albert8b72aef2015-03-23 19:13:21 -07002506class BlockDifference(object):
2507 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002508 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002509 self.tgt = tgt
2510 self.src = src
2511 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002512 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002513 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002514
Tao Baodd2a5892015-03-12 12:32:37 -07002515 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002516 version = max(
2517 int(i) for i in
2518 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002519 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002520 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002521
Tianjie Xu41976c72019-07-03 13:57:01 -07002522 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2523 version=self.version,
2524 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002525 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002526 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002527 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002528 self.touched_src_ranges = b.touched_src_ranges
2529 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002530
Yifan Hong10c530d2018-12-27 17:34:18 -08002531 # On devices with dynamic partitions, for new partitions,
2532 # src is None but OPTIONS.source_info_dict is not.
2533 if OPTIONS.source_info_dict is None:
2534 is_dynamic_build = OPTIONS.info_dict.get(
2535 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002536 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002537 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002538 is_dynamic_build = OPTIONS.source_info_dict.get(
2539 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002540 is_dynamic_source = partition in shlex.split(
2541 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002542
Yifan Hongbb2658d2019-01-25 12:30:58 -08002543 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002544 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2545
Yifan Hongbb2658d2019-01-25 12:30:58 -08002546 # For dynamic partitions builds, check partition list in both source
2547 # and target build because new partitions may be added, and existing
2548 # partitions may be removed.
2549 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2550
Yifan Hong10c530d2018-12-27 17:34:18 -08002551 if is_dynamic:
2552 self.device = 'map_partition("%s")' % partition
2553 else:
2554 if OPTIONS.source_info_dict is None:
2555 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2556 else:
2557 _, device_path = GetTypeAndDevice("/" + partition,
2558 OPTIONS.source_info_dict)
2559 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002560
Tao Baod8d14be2016-02-04 14:26:02 -08002561 @property
2562 def required_cache(self):
2563 return self._required_cache
2564
Tao Bao76def242017-11-21 09:25:31 -08002565 def WriteScript(self, script, output_zip, progress=None,
2566 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002567 if not self.src:
2568 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002569 script.Print("Patching %s image unconditionally..." % (self.partition,))
2570 else:
2571 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002572
Dan Albert8b72aef2015-03-23 19:13:21 -07002573 if progress:
2574 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002575 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002576
2577 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002578 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002579
Tao Bao9bc6bb22015-11-09 16:58:28 -08002580 def WriteStrictVerifyScript(self, script):
2581 """Verify all the blocks in the care_map, including clobbered blocks.
2582
2583 This differs from the WriteVerifyScript() function: a) it prints different
2584 error messages; b) it doesn't allow half-way updated images to pass the
2585 verification."""
2586
2587 partition = self.partition
2588 script.Print("Verifying %s..." % (partition,))
2589 ranges = self.tgt.care_map
2590 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002591 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002592 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2593 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002594 self.device, ranges_str,
2595 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002596 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002597 script.AppendExtra("")
2598
Tao Baod522bdc2016-04-12 15:53:16 -07002599 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002600 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002601
2602 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002603 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002604 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002605
2606 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002607 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002608 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002609 ranges = self.touched_src_ranges
2610 expected_sha1 = self.touched_src_sha1
2611 else:
2612 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2613 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002614
2615 # No blocks to be checked, skipping.
2616 if not ranges:
2617 return
2618
Tao Bao5ece99d2015-05-12 11:42:31 -07002619 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002620 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002621 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002622 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2623 '"%s.patch.dat")) then' % (
2624 self.device, ranges_str, expected_sha1,
2625 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002626 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002627 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002628
Tianjie Xufc3422a2015-12-15 11:53:59 -08002629 if self.version >= 4:
2630
2631 # Bug: 21124327
2632 # When generating incrementals for the system and vendor partitions in
2633 # version 4 or newer, explicitly check the first block (which contains
2634 # the superblock) of the partition to see if it's what we expect. If
2635 # this check fails, give an explicit log message about the partition
2636 # having been remounted R/W (the most likely explanation).
2637 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002638 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002639
2640 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002641 if partition == "system":
2642 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2643 else:
2644 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002645 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002646 'ifelse (block_image_recover({device}, "{ranges}") && '
2647 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002648 'package_extract_file("{partition}.transfer.list"), '
2649 '"{partition}.new.dat", "{partition}.patch.dat"), '
2650 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002651 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002652 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002653 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002654
Tao Baodd2a5892015-03-12 12:32:37 -07002655 # Abort the OTA update. Note that the incremental OTA cannot be applied
2656 # even if it may match the checksum of the target partition.
2657 # a) If version < 3, operations like move and erase will make changes
2658 # unconditionally and damage the partition.
2659 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002660 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002661 if partition == "system":
2662 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2663 else:
2664 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2665 script.AppendExtra((
2666 'abort("E%d: %s partition has unexpected contents");\n'
2667 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002668
Yifan Hong10c530d2018-12-27 17:34:18 -08002669 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002670 partition = self.partition
2671 script.Print('Verifying the updated %s image...' % (partition,))
2672 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2673 ranges = self.tgt.care_map
2674 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002675 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002676 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002677 self.device, ranges_str,
2678 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002679
2680 # Bug: 20881595
2681 # Verify that extended blocks are really zeroed out.
2682 if self.tgt.extended:
2683 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002684 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002685 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002686 self.device, ranges_str,
2687 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002688 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002689 if partition == "system":
2690 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2691 else:
2692 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002693 script.AppendExtra(
2694 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002695 ' abort("E%d: %s partition has unexpected non-zero contents after '
2696 'OTA update");\n'
2697 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002698 else:
2699 script.Print('Verified the updated %s image.' % (partition,))
2700
Tianjie Xu209db462016-05-24 17:34:52 -07002701 if partition == "system":
2702 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2703 else:
2704 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2705
Tao Bao5fcaaef2015-06-01 13:40:49 -07002706 script.AppendExtra(
2707 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002708 ' abort("E%d: %s partition has unexpected contents after OTA '
2709 'update");\n'
2710 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002711
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002712 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002713 ZipWrite(output_zip,
2714 '{}.transfer.list'.format(self.path),
2715 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002716
Tao Bao76def242017-11-21 09:25:31 -08002717 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2718 # its size. Quailty 9 almost triples the compression time but doesn't
2719 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002720 # zip | brotli(quality 6) | brotli(quality 9)
2721 # compressed_size: 942M | 869M (~8% reduced) | 854M
2722 # compression_time: 75s | 265s | 719s
2723 # decompression_time: 15s | 25s | 25s
2724
2725 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002726 brotli_cmd = ['brotli', '--quality=6',
2727 '--output={}.new.dat.br'.format(self.path),
2728 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002729 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002730 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002731
2732 new_data_name = '{}.new.dat.br'.format(self.partition)
2733 ZipWrite(output_zip,
2734 '{}.new.dat.br'.format(self.path),
2735 new_data_name,
2736 compress_type=zipfile.ZIP_STORED)
2737 else:
2738 new_data_name = '{}.new.dat'.format(self.partition)
2739 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2740
Dan Albert8e0178d2015-01-27 15:53:15 -08002741 ZipWrite(output_zip,
2742 '{}.patch.dat'.format(self.path),
2743 '{}.patch.dat'.format(self.partition),
2744 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002745
Tianjie Xu209db462016-05-24 17:34:52 -07002746 if self.partition == "system":
2747 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2748 else:
2749 code = ErrorCode.VENDOR_UPDATE_FAILURE
2750
Yifan Hong10c530d2018-12-27 17:34:18 -08002751 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002752 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002753 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002754 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002755 device=self.device, partition=self.partition,
2756 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002757 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002758
Dan Albert8b72aef2015-03-23 19:13:21 -07002759 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002760 data = source.ReadRangeSet(ranges)
2761 ctx = sha1()
2762
2763 for p in data:
2764 ctx.update(p)
2765
2766 return ctx.hexdigest()
2767
Tao Baoe9b61912015-07-09 17:37:49 -07002768 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2769 """Return the hash value for all zero blocks."""
2770 zero_block = '\x00' * 4096
2771 ctx = sha1()
2772 for _ in range(num_blocks):
2773 ctx.update(zero_block)
2774
2775 return ctx.hexdigest()
2776
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002777
Tianjie Xu41976c72019-07-03 13:57:01 -07002778# Expose these two classes to support vendor-specific scripts
2779DataImage = images.DataImage
2780EmptyImage = images.EmptyImage
2781
Tao Bao76def242017-11-21 09:25:31 -08002782
Doug Zongker96a57e72010-09-26 14:57:41 -07002783# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002784PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002785 "ext4": "EMMC",
2786 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002787 "f2fs": "EMMC",
2788 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002789}
Doug Zongker96a57e72010-09-26 14:57:41 -07002790
Tao Bao76def242017-11-21 09:25:31 -08002791
Doug Zongker96a57e72010-09-26 14:57:41 -07002792def GetTypeAndDevice(mount_point, info):
2793 fstab = info["fstab"]
2794 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002795 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2796 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002797 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002798 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002799
2800
2801def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002802 """Parses and converts a PEM-encoded certificate into DER-encoded.
2803
2804 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2805
2806 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002807 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002808 """
2809 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002810 save = False
2811 for line in data.split("\n"):
2812 if "--END CERTIFICATE--" in line:
2813 break
2814 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002815 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002816 if "--BEGIN CERTIFICATE--" in line:
2817 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002818 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002819 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002820
Tao Bao04e1f012018-02-04 12:13:35 -08002821
2822def ExtractPublicKey(cert):
2823 """Extracts the public key (PEM-encoded) from the given certificate file.
2824
2825 Args:
2826 cert: The certificate filename.
2827
2828 Returns:
2829 The public key string.
2830
2831 Raises:
2832 AssertionError: On non-zero return from 'openssl'.
2833 """
2834 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2835 # While openssl 1.1 writes the key into the given filename followed by '-out',
2836 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2837 # stdout instead.
2838 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2839 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2840 pubkey, stderrdata = proc.communicate()
2841 assert proc.returncode == 0, \
2842 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2843 return pubkey
2844
2845
Tao Bao1ac886e2019-06-26 11:58:22 -07002846def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002847 """Extracts the AVB public key from the given public or private key.
2848
2849 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002850 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002851 key: The input key file, which should be PEM-encoded public or private key.
2852
2853 Returns:
2854 The path to the extracted AVB public key file.
2855 """
2856 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2857 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002858 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002859 return output
2860
2861
Doug Zongker412c02f2014-02-13 10:58:24 -08002862def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2863 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002864 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002865
Tao Bao6d5d6232018-03-09 17:04:42 -08002866 Most of the space in the boot and recovery images is just the kernel, which is
2867 identical for the two, so the resulting patch should be efficient. Add it to
2868 the output zip, along with a shell script that is run from init.rc on first
2869 boot to actually do the patching and install the new recovery image.
2870
2871 Args:
2872 input_dir: The top-level input directory of the target-files.zip.
2873 output_sink: The callback function that writes the result.
2874 recovery_img: File object for the recovery image.
2875 boot_img: File objects for the boot image.
2876 info_dict: A dict returned by common.LoadInfoDict() on the input
2877 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002878 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002879 if info_dict is None:
2880 info_dict = OPTIONS.info_dict
2881
Tao Bao6d5d6232018-03-09 17:04:42 -08002882 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002883 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2884
2885 if board_uses_vendorimage:
2886 # In this case, the output sink is rooted at VENDOR
2887 recovery_img_path = "etc/recovery.img"
2888 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2889 sh_dir = "bin"
2890 else:
2891 # In this case the output sink is rooted at SYSTEM
2892 recovery_img_path = "vendor/etc/recovery.img"
2893 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2894 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002895
Tao Baof2cffbd2015-07-22 12:33:18 -07002896 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002897 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002898
2899 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002900 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002901 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002902 # With system-root-image, boot and recovery images will have mismatching
2903 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2904 # to handle such a case.
2905 if system_root_image:
2906 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002907 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002908 assert not os.path.exists(path)
2909 else:
2910 diff_program = ["imgdiff"]
2911 if os.path.exists(path):
2912 diff_program.append("-b")
2913 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002914 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002915 else:
2916 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002917
2918 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2919 _, _, patch = d.ComputePatch()
2920 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002921
Dan Albertebb19aa2015-03-27 19:11:53 -07002922 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002923 # The following GetTypeAndDevice()s need to use the path in the target
2924 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002925 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2926 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2927 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002928 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002929
Tao Baof2cffbd2015-07-22 12:33:18 -07002930 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002931
2932 # Note that we use /vendor to refer to the recovery resources. This will
2933 # work for a separate vendor partition mounted at /vendor or a
2934 # /system/vendor subdirectory on the system partition, for which init will
2935 # create a symlink from /vendor to /system/vendor.
2936
2937 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002938if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2939 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002940 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002941 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2942 log -t recovery "Installing new recovery image: succeeded" || \\
2943 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002944else
2945 log -t recovery "Recovery image already installed"
2946fi
2947""" % {'type': recovery_type,
2948 'device': recovery_device,
2949 'sha1': recovery_img.sha1,
2950 'size': recovery_img.size}
2951 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002952 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002953if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2954 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002955 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002956 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2957 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2958 log -t recovery "Installing new recovery image: succeeded" || \\
2959 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002960else
2961 log -t recovery "Recovery image already installed"
2962fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002963""" % {'boot_size': boot_img.size,
2964 'boot_sha1': boot_img.sha1,
2965 'recovery_size': recovery_img.size,
2966 'recovery_sha1': recovery_img.sha1,
2967 'boot_type': boot_type,
2968 'boot_device': boot_device,
2969 'recovery_type': recovery_type,
2970 'recovery_device': recovery_device,
2971 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002972
Bill Peckhame868aec2019-09-17 17:06:47 -07002973 # The install script location moved from /system/etc to /system/bin in the L
2974 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2975 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002976
Tao Bao32fcdab2018-10-12 10:30:39 -07002977 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002978
Tao Baoda30cfa2017-12-01 16:19:46 -08002979 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002980
2981
2982class DynamicPartitionUpdate(object):
2983 def __init__(self, src_group=None, tgt_group=None, progress=None,
2984 block_difference=None):
2985 self.src_group = src_group
2986 self.tgt_group = tgt_group
2987 self.progress = progress
2988 self.block_difference = block_difference
2989
2990 @property
2991 def src_size(self):
2992 if not self.block_difference:
2993 return 0
2994 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2995
2996 @property
2997 def tgt_size(self):
2998 if not self.block_difference:
2999 return 0
3000 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3001
3002 @staticmethod
3003 def _GetSparseImageSize(img):
3004 if not img:
3005 return 0
3006 return img.blocksize * img.total_blocks
3007
3008
3009class DynamicGroupUpdate(object):
3010 def __init__(self, src_size=None, tgt_size=None):
3011 # None: group does not exist. 0: no size limits.
3012 self.src_size = src_size
3013 self.tgt_size = tgt_size
3014
3015
3016class DynamicPartitionsDifference(object):
3017 def __init__(self, info_dict, block_diffs, progress_dict=None,
3018 source_info_dict=None):
3019 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003020 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003021
3022 self._remove_all_before_apply = False
3023 if source_info_dict is None:
3024 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003025 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003026
Tao Baof1113e92019-06-18 12:10:14 -07003027 block_diff_dict = collections.OrderedDict(
3028 [(e.partition, e) for e in block_diffs])
3029
Yifan Hong10c530d2018-12-27 17:34:18 -08003030 assert len(block_diff_dict) == len(block_diffs), \
3031 "Duplicated BlockDifference object for {}".format(
3032 [partition for partition, count in
3033 collections.Counter(e.partition for e in block_diffs).items()
3034 if count > 1])
3035
Yifan Hong79997e52019-01-23 16:56:19 -08003036 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003037
3038 for p, block_diff in block_diff_dict.items():
3039 self._partition_updates[p] = DynamicPartitionUpdate()
3040 self._partition_updates[p].block_difference = block_diff
3041
3042 for p, progress in progress_dict.items():
3043 if p in self._partition_updates:
3044 self._partition_updates[p].progress = progress
3045
3046 tgt_groups = shlex.split(info_dict.get(
3047 "super_partition_groups", "").strip())
3048 src_groups = shlex.split(source_info_dict.get(
3049 "super_partition_groups", "").strip())
3050
3051 for g in tgt_groups:
3052 for p in shlex.split(info_dict.get(
3053 "super_%s_partition_list" % g, "").strip()):
3054 assert p in self._partition_updates, \
3055 "{} is in target super_{}_partition_list but no BlockDifference " \
3056 "object is provided.".format(p, g)
3057 self._partition_updates[p].tgt_group = g
3058
3059 for g in src_groups:
3060 for p in shlex.split(source_info_dict.get(
3061 "super_%s_partition_list" % g, "").strip()):
3062 assert p in self._partition_updates, \
3063 "{} is in source super_{}_partition_list but no BlockDifference " \
3064 "object is provided.".format(p, g)
3065 self._partition_updates[p].src_group = g
3066
Yifan Hong45433e42019-01-18 13:55:25 -08003067 target_dynamic_partitions = set(shlex.split(info_dict.get(
3068 "dynamic_partition_list", "").strip()))
3069 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3070 if u.tgt_size)
3071 assert block_diffs_with_target == target_dynamic_partitions, \
3072 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3073 list(target_dynamic_partitions), list(block_diffs_with_target))
3074
3075 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3076 "dynamic_partition_list", "").strip()))
3077 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3078 if u.src_size)
3079 assert block_diffs_with_source == source_dynamic_partitions, \
3080 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3081 list(source_dynamic_partitions), list(block_diffs_with_source))
3082
Yifan Hong10c530d2018-12-27 17:34:18 -08003083 if self._partition_updates:
3084 logger.info("Updating dynamic partitions %s",
3085 self._partition_updates.keys())
3086
Yifan Hong79997e52019-01-23 16:56:19 -08003087 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003088
3089 for g in tgt_groups:
3090 self._group_updates[g] = DynamicGroupUpdate()
3091 self._group_updates[g].tgt_size = int(info_dict.get(
3092 "super_%s_group_size" % g, "0").strip())
3093
3094 for g in src_groups:
3095 if g not in self._group_updates:
3096 self._group_updates[g] = DynamicGroupUpdate()
3097 self._group_updates[g].src_size = int(source_info_dict.get(
3098 "super_%s_group_size" % g, "0").strip())
3099
3100 self._Compute()
3101
3102 def WriteScript(self, script, output_zip, write_verify_script=False):
3103 script.Comment('--- Start patching dynamic partitions ---')
3104 for p, u in self._partition_updates.items():
3105 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3106 script.Comment('Patch partition %s' % p)
3107 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3108 write_verify_script=False)
3109
3110 op_list_path = MakeTempFile()
3111 with open(op_list_path, 'w') as f:
3112 for line in self._op_list:
3113 f.write('{}\n'.format(line))
3114
3115 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3116
3117 script.Comment('Update dynamic partition metadata')
3118 script.AppendExtra('assert(update_dynamic_partitions('
3119 'package_extract_file("dynamic_partitions_op_list")));')
3120
3121 if write_verify_script:
3122 for p, u in self._partition_updates.items():
3123 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3124 u.block_difference.WritePostInstallVerifyScript(script)
3125 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3126
3127 for p, u in self._partition_updates.items():
3128 if u.tgt_size and u.src_size <= u.tgt_size:
3129 script.Comment('Patch partition %s' % p)
3130 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3131 write_verify_script=write_verify_script)
3132 if write_verify_script:
3133 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3134
3135 script.Comment('--- End patching dynamic partitions ---')
3136
3137 def _Compute(self):
3138 self._op_list = list()
3139
3140 def append(line):
3141 self._op_list.append(line)
3142
3143 def comment(line):
3144 self._op_list.append("# %s" % line)
3145
3146 if self._remove_all_before_apply:
3147 comment('Remove all existing dynamic partitions and groups before '
3148 'applying full OTA')
3149 append('remove_all_groups')
3150
3151 for p, u in self._partition_updates.items():
3152 if u.src_group and not u.tgt_group:
3153 append('remove %s' % p)
3154
3155 for p, u in self._partition_updates.items():
3156 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3157 comment('Move partition %s from %s to default' % (p, u.src_group))
3158 append('move %s default' % p)
3159
3160 for p, u in self._partition_updates.items():
3161 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3162 comment('Shrink partition %s from %d to %d' %
3163 (p, u.src_size, u.tgt_size))
3164 append('resize %s %s' % (p, u.tgt_size))
3165
3166 for g, u in self._group_updates.items():
3167 if u.src_size is not None and u.tgt_size is None:
3168 append('remove_group %s' % g)
3169 if (u.src_size is not None and u.tgt_size is not None and
3170 u.src_size > u.tgt_size):
3171 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3172 append('resize_group %s %d' % (g, u.tgt_size))
3173
3174 for g, u in self._group_updates.items():
3175 if u.src_size is None and u.tgt_size is not None:
3176 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3177 append('add_group %s %d' % (g, u.tgt_size))
3178 if (u.src_size is not None and u.tgt_size is not None and
3179 u.src_size < u.tgt_size):
3180 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3181 append('resize_group %s %d' % (g, u.tgt_size))
3182
3183 for p, u in self._partition_updates.items():
3184 if u.tgt_group and not u.src_group:
3185 comment('Add partition %s to group %s' % (p, u.tgt_group))
3186 append('add %s %s' % (p, u.tgt_group))
3187
3188 for p, u in self._partition_updates.items():
3189 if u.tgt_size and u.src_size < u.tgt_size:
3190 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3191 append('resize %s %d' % (p, u.tgt_size))
3192
3193 for p, u in self._partition_updates.items():
3194 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3195 comment('Move partition %s from default to %s' %
3196 (p, u.tgt_group))
3197 append('move %s %s' % (p, u.tgt_group))