blob: cdb10fa7b609e506d584d000eb5bdd9ee6c71746 [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")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301148 if not args:
1149 # Fall back to "mkbootimg_args" for recovery image
1150 # in case "recovery_mkbootimg_args" is not set.
1151 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001152 else:
1153 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001154 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001155 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001156
Tao Bao76def242017-11-21 09:25:31 -08001157 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001158 if args and args.strip():
1159 cmd.extend(shlex.split(args))
1160
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001161 if has_ramdisk:
1162 cmd.extend(["--ramdisk", ramdisk_img.name])
1163
Tao Baod95e9fd2015-03-29 23:07:41 -07001164 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001165 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001166 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001167 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001168 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001169 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001170
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001171 if partition_name == "recovery":
1172 if info_dict.get("include_recovery_dtbo") == "true":
1173 fn = os.path.join(sourcedir, "recovery_dtbo")
1174 cmd.extend(["--recovery_dtbo", fn])
1175 if info_dict.get("include_recovery_acpio") == "true":
1176 fn = os.path.join(sourcedir, "recovery_acpio")
1177 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001178
Tao Bao986ee862018-10-04 15:46:16 -07001179 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001180
Tao Bao76def242017-11-21 09:25:31 -08001181 if (info_dict.get("boot_signer") == "true" and
1182 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001183 # Hard-code the path as "/boot" for two-step special recovery image (which
1184 # will be loaded into /boot during the two-step OTA).
1185 if two_step_image:
1186 path = "/boot"
1187 else:
Tao Baobf70c312017-07-11 17:27:55 -07001188 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001189 cmd = [OPTIONS.boot_signer_path]
1190 cmd.extend(OPTIONS.boot_signer_args)
1191 cmd.extend([path, img.name,
1192 info_dict["verity_key"] + ".pk8",
1193 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001194 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001195
Tao Baod95e9fd2015-03-29 23:07:41 -07001196 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001197 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001198 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001199 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001200 # We have switched from the prebuilt futility binary to using the tool
1201 # (futility-host) built from the source. Override the setting in the old
1202 # TF.zip.
1203 futility = info_dict["futility"]
1204 if futility.startswith("prebuilts/"):
1205 futility = "futility-host"
1206 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001207 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001208 info_dict["vboot_key"] + ".vbprivk",
1209 info_dict["vboot_subkey"] + ".vbprivk",
1210 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001211 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001212 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001213
Tao Baof3282b42015-04-01 11:21:55 -07001214 # Clean up the temp files.
1215 img_unsigned.close()
1216 img_keyblock.close()
1217
David Zeuthen8fecb282017-12-01 16:24:01 -05001218 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001219 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001220 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001221 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001222 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001223 "--partition_size", str(part_size), "--partition_name",
1224 partition_name]
1225 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001226 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001227 if args and args.strip():
1228 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001229 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001230
1231 img.seek(os.SEEK_SET, 0)
1232 data = img.read()
1233
1234 if has_ramdisk:
1235 ramdisk_img.close()
1236 img.close()
1237
1238 return data
1239
1240
Doug Zongkerd5131602012-08-02 14:46:42 -07001241def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001242 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001243 """Return a File object with the desired bootable image.
1244
1245 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1246 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1247 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001248
Doug Zongker55d93282011-01-25 17:03:34 -08001249 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1250 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001251 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001252 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001253
1254 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1255 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001256 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001257 return File.FromLocalFile(name, prebuilt_path)
1258
Tao Bao32fcdab2018-10-12 10:30:39 -07001259 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001260
1261 if info_dict is None:
1262 info_dict = OPTIONS.info_dict
1263
1264 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001265 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1266 # for recovery.
1267 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1268 prebuilt_name != "boot.img" or
1269 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001270
Doug Zongker6f1d0312014-08-22 08:07:12 -07001271 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001272 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001273 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001274 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001275 if data:
1276 return File(name, data)
1277 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001278
Doug Zongkereef39442009-04-02 12:14:19 -07001279
Steve Mucklee1b10862019-07-10 10:49:37 -07001280def _BuildVendorBootImage(sourcedir, info_dict=None):
1281 """Build a vendor boot image from the specified sourcedir.
1282
1283 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1284 turn them into a vendor boot image.
1285
1286 Return the image data, or None if sourcedir does not appear to contains files
1287 for building the requested image.
1288 """
1289
1290 if info_dict is None:
1291 info_dict = OPTIONS.info_dict
1292
1293 img = tempfile.NamedTemporaryFile()
1294
1295 ramdisk_img = _MakeRamdisk(sourcedir)
1296
1297 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1298 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1299
1300 cmd = [mkbootimg]
1301
1302 fn = os.path.join(sourcedir, "dtb")
1303 if os.access(fn, os.F_OK):
1304 cmd.append("--dtb")
1305 cmd.append(fn)
1306
1307 fn = os.path.join(sourcedir, "vendor_cmdline")
1308 if os.access(fn, os.F_OK):
1309 cmd.append("--vendor_cmdline")
1310 cmd.append(open(fn).read().rstrip("\n"))
1311
1312 fn = os.path.join(sourcedir, "base")
1313 if os.access(fn, os.F_OK):
1314 cmd.append("--base")
1315 cmd.append(open(fn).read().rstrip("\n"))
1316
1317 fn = os.path.join(sourcedir, "pagesize")
1318 if os.access(fn, os.F_OK):
1319 cmd.append("--pagesize")
1320 cmd.append(open(fn).read().rstrip("\n"))
1321
1322 args = info_dict.get("mkbootimg_args")
1323 if args and args.strip():
1324 cmd.extend(shlex.split(args))
1325
1326 args = info_dict.get("mkbootimg_version_args")
1327 if args and args.strip():
1328 cmd.extend(shlex.split(args))
1329
1330 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1331 cmd.extend(["--vendor_boot", img.name])
1332
1333 RunAndCheckOutput(cmd)
1334
1335 # AVB: if enabled, calculate and add hash.
1336 if info_dict.get("avb_enable") == "true":
1337 avbtool = info_dict["avb_avbtool"]
1338 part_size = info_dict["vendor_boot_size"]
1339 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001340 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001341 AppendAVBSigningArgs(cmd, "vendor_boot")
1342 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1343 if args and args.strip():
1344 cmd.extend(shlex.split(args))
1345 RunAndCheckOutput(cmd)
1346
1347 img.seek(os.SEEK_SET, 0)
1348 data = img.read()
1349
1350 ramdisk_img.close()
1351 img.close()
1352
1353 return data
1354
1355
1356def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1357 info_dict=None):
1358 """Return a File object with the desired vendor boot image.
1359
1360 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1361 the source files in 'unpack_dir'/'tree_subdir'."""
1362
1363 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1364 if os.path.exists(prebuilt_path):
1365 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1366 return File.FromLocalFile(name, prebuilt_path)
1367
1368 logger.info("building image from target_files %s...", tree_subdir)
1369
1370 if info_dict is None:
1371 info_dict = OPTIONS.info_dict
1372
1373 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1374 if data:
1375 return File(name, data)
1376 return None
1377
1378
Narayan Kamatha07bf042017-08-14 14:49:21 +01001379def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001380 """Gunzips the given gzip compressed file to a given output file."""
1381 with gzip.open(in_filename, "rb") as in_file, \
1382 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001383 shutil.copyfileobj(in_file, out_file)
1384
1385
Tao Bao0ff15de2019-03-20 11:26:06 -07001386def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001387 """Unzips the archive to the given directory.
1388
1389 Args:
1390 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001391 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001392 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1393 archvie. Non-matching patterns will be filtered out. If there's no match
1394 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001395 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001396 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001397 if patterns is not None:
1398 # Filter out non-matching patterns. unzip will complain otherwise.
1399 with zipfile.ZipFile(filename) as input_zip:
1400 names = input_zip.namelist()
1401 filtered = [
1402 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1403
1404 # There isn't any matching files. Don't unzip anything.
1405 if not filtered:
1406 return
1407 cmd.extend(filtered)
1408
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001409 RunAndCheckOutput(cmd)
1410
1411
Doug Zongker75f17362009-12-08 13:46:44 -08001412def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001413 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001414
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001415 Args:
1416 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1417 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1418
1419 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1420 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001421
Tao Bao1c830bf2017-12-25 10:43:47 -08001422 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001423 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001424 """
Doug Zongkereef39442009-04-02 12:14:19 -07001425
Tao Bao1c830bf2017-12-25 10:43:47 -08001426 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001427 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1428 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001429 UnzipToDir(m.group(1), tmp, pattern)
1430 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001431 filename = m.group(1)
1432 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001433 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001434
Tao Baodba59ee2018-01-09 13:21:02 -08001435 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001436
1437
Yifan Hong8a66a712019-04-04 15:37:57 -07001438def GetUserImage(which, tmpdir, input_zip,
1439 info_dict=None,
1440 allow_shared_blocks=None,
1441 hashtree_info_generator=None,
1442 reset_file_map=False):
1443 """Returns an Image object suitable for passing to BlockImageDiff.
1444
1445 This function loads the specified image from the given path. If the specified
1446 image is sparse, it also performs additional processing for OTA purpose. For
1447 example, it always adds block 0 to clobbered blocks list. It also detects
1448 files that cannot be reconstructed from the block list, for whom we should
1449 avoid applying imgdiff.
1450
1451 Args:
1452 which: The partition name.
1453 tmpdir: The directory that contains the prebuilt image and block map file.
1454 input_zip: The target-files ZIP archive.
1455 info_dict: The dict to be looked up for relevant info.
1456 allow_shared_blocks: If image is sparse, whether having shared blocks is
1457 allowed. If none, it is looked up from info_dict.
1458 hashtree_info_generator: If present and image is sparse, generates the
1459 hashtree_info for this sparse image.
1460 reset_file_map: If true and image is sparse, reset file map before returning
1461 the image.
1462 Returns:
1463 A Image object. If it is a sparse image and reset_file_map is False, the
1464 image will have file_map info loaded.
1465 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001466 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001467 info_dict = LoadInfoDict(input_zip)
1468
1469 is_sparse = info_dict.get("extfs_sparse_flag")
1470
1471 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1472 # shared blocks (i.e. some blocks will show up in multiple files' block
1473 # list). We can only allocate such shared blocks to the first "owner", and
1474 # disable imgdiff for all later occurrences.
1475 if allow_shared_blocks is None:
1476 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1477
1478 if is_sparse:
1479 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1480 hashtree_info_generator)
1481 if reset_file_map:
1482 img.ResetFileMap()
1483 return img
1484 else:
1485 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1486
1487
1488def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1489 """Returns a Image object suitable for passing to BlockImageDiff.
1490
1491 This function loads the specified non-sparse image from the given path.
1492
1493 Args:
1494 which: The partition name.
1495 tmpdir: The directory that contains the prebuilt image and block map file.
1496 Returns:
1497 A Image object.
1498 """
1499 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1500 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1501
1502 # The image and map files must have been created prior to calling
1503 # ota_from_target_files.py (since LMP).
1504 assert os.path.exists(path) and os.path.exists(mappath)
1505
Tianjie Xu41976c72019-07-03 13:57:01 -07001506 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1507
Yifan Hong8a66a712019-04-04 15:37:57 -07001508
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001509def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1510 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001511 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1512
1513 This function loads the specified sparse image from the given path, and
1514 performs additional processing for OTA purpose. For example, it always adds
1515 block 0 to clobbered blocks list. It also detects files that cannot be
1516 reconstructed from the block list, for whom we should avoid applying imgdiff.
1517
1518 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001519 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001520 tmpdir: The directory that contains the prebuilt image and block map file.
1521 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001522 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001523 hashtree_info_generator: If present, generates the hashtree_info for this
1524 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001525 Returns:
1526 A SparseImage object, with file_map info loaded.
1527 """
Tao Baoc765cca2018-01-31 17:32:40 -08001528 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1529 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1530
1531 # The image and map files must have been created prior to calling
1532 # ota_from_target_files.py (since LMP).
1533 assert os.path.exists(path) and os.path.exists(mappath)
1534
1535 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1536 # it to clobbered_blocks so that it will be written to the target
1537 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1538 clobbered_blocks = "0"
1539
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001540 image = sparse_img.SparseImage(
1541 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1542 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001543
1544 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1545 # if they contain all zeros. We can't reconstruct such a file from its block
1546 # list. Tag such entries accordingly. (Bug: 65213616)
1547 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001548 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001549 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001550 continue
1551
Tom Cherryd14b8952018-08-09 14:26:00 -07001552 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1553 # filename listed in system.map may contain an additional leading slash
1554 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1555 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001556 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001557
Tom Cherryd14b8952018-08-09 14:26:00 -07001558 # Special handling another case, where files not under /system
1559 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001560 if which == 'system' and not arcname.startswith('SYSTEM'):
1561 arcname = 'ROOT/' + arcname
1562
1563 assert arcname in input_zip.namelist(), \
1564 "Failed to find the ZIP entry for {}".format(entry)
1565
Tao Baoc765cca2018-01-31 17:32:40 -08001566 info = input_zip.getinfo(arcname)
1567 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001568
1569 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001570 # image, check the original block list to determine its completeness. Note
1571 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001572 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001573 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001574
Tao Baoc765cca2018-01-31 17:32:40 -08001575 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1576 ranges.extra['incomplete'] = True
1577
1578 return image
1579
1580
Doug Zongkereef39442009-04-02 12:14:19 -07001581def GetKeyPasswords(keylist):
1582 """Given a list of keys, prompt the user to enter passwords for
1583 those which require them. Return a {key: password} dict. password
1584 will be None if the key has no password."""
1585
Doug Zongker8ce7c252009-05-22 13:34:54 -07001586 no_passwords = []
1587 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001588 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001589 devnull = open("/dev/null", "w+b")
1590 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001591 # We don't need a password for things that aren't really keys.
1592 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001593 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001594 continue
1595
T.R. Fullhart37e10522013-03-18 10:31:26 -07001596 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001597 "-inform", "DER", "-nocrypt"],
1598 stdin=devnull.fileno(),
1599 stdout=devnull.fileno(),
1600 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001601 p.communicate()
1602 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001603 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001604 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001605 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001606 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1607 "-inform", "DER", "-passin", "pass:"],
1608 stdin=devnull.fileno(),
1609 stdout=devnull.fileno(),
1610 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001611 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001612 if p.returncode == 0:
1613 # Encrypted key with empty string as password.
1614 key_passwords[k] = ''
1615 elif stderr.startswith('Error decrypting key'):
1616 # Definitely encrypted key.
1617 # It would have said "Error reading key" if it didn't parse correctly.
1618 need_passwords.append(k)
1619 else:
1620 # Potentially, a type of key that openssl doesn't understand.
1621 # We'll let the routines in signapk.jar handle it.
1622 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001623 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001624
T.R. Fullhart37e10522013-03-18 10:31:26 -07001625 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001626 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001627 return key_passwords
1628
1629
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001630def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001631 """Gets the minSdkVersion declared in the APK.
1632
changho.shin0f125362019-07-08 10:59:00 +09001633 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001634 This can be both a decimal number (API Level) or a codename.
1635
1636 Args:
1637 apk_name: The APK filename.
1638
1639 Returns:
1640 The parsed SDK version string.
1641
1642 Raises:
1643 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001644 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001645 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001646 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001647 stderr=subprocess.PIPE)
1648 stdoutdata, stderrdata = proc.communicate()
1649 if proc.returncode != 0:
1650 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001651 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001652 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001653
Tao Baof47bf0f2018-03-21 23:28:51 -07001654 for line in stdoutdata.split("\n"):
1655 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001656 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1657 if m:
1658 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001659 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001660
1661
1662def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001663 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001664
Tao Baof47bf0f2018-03-21 23:28:51 -07001665 If minSdkVersion is set to a codename, it is translated to a number using the
1666 provided map.
1667
1668 Args:
1669 apk_name: The APK filename.
1670
1671 Returns:
1672 The parsed SDK version number.
1673
1674 Raises:
1675 ExternalError: On failing to get the min SDK version number.
1676 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001677 version = GetMinSdkVersion(apk_name)
1678 try:
1679 return int(version)
1680 except ValueError:
1681 # Not a decimal number. Codename?
1682 if version in codename_to_api_level_map:
1683 return codename_to_api_level_map[version]
1684 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001685 raise ExternalError(
1686 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1687 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001688
1689
1690def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001691 codename_to_api_level_map=None, whole_file=False,
1692 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001693 """Sign the input_name zip/jar/apk, producing output_name. Use the
1694 given key and password (the latter may be None if the key does not
1695 have a password.
1696
Doug Zongker951495f2009-08-14 12:44:19 -07001697 If whole_file is true, use the "-w" option to SignApk to embed a
1698 signature that covers the whole file in the archive comment of the
1699 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001700
1701 min_api_level is the API Level (int) of the oldest platform this file may end
1702 up on. If not specified for an APK, the API Level is obtained by interpreting
1703 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1704
1705 codename_to_api_level_map is needed to translate the codename which may be
1706 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001707
1708 Caller may optionally specify extra args to be passed to SignApk, which
1709 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001710 """
Tao Bao76def242017-11-21 09:25:31 -08001711 if codename_to_api_level_map is None:
1712 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001713 if extra_signapk_args is None:
1714 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001715
Alex Klyubin9667b182015-12-10 13:38:50 -08001716 java_library_path = os.path.join(
1717 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1718
Tao Baoe95540e2016-11-08 12:08:53 -08001719 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1720 ["-Djava.library.path=" + java_library_path,
1721 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001722 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001723 if whole_file:
1724 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001725
1726 min_sdk_version = min_api_level
1727 if min_sdk_version is None:
1728 if not whole_file:
1729 min_sdk_version = GetMinSdkVersionInt(
1730 input_name, codename_to_api_level_map)
1731 if min_sdk_version is not None:
1732 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1733
T.R. Fullhart37e10522013-03-18 10:31:26 -07001734 cmd.extend([key + OPTIONS.public_key_suffix,
1735 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001736 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001737
Tao Bao73dd4f42018-10-04 16:25:33 -07001738 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001739 if password is not None:
1740 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001741 stdoutdata, _ = proc.communicate(password)
1742 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001743 raise ExternalError(
1744 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001745 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001746
Doug Zongkereef39442009-04-02 12:14:19 -07001747
Doug Zongker37974732010-09-16 17:44:38 -07001748def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001749 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001750
Tao Bao9dd909e2017-11-14 11:27:32 -08001751 For non-AVB images, raise exception if the data is too big. Print a warning
1752 if the data is nearing the maximum size.
1753
1754 For AVB images, the actual image size should be identical to the limit.
1755
1756 Args:
1757 data: A string that contains all the data for the partition.
1758 target: The partition name. The ".img" suffix is optional.
1759 info_dict: The dict to be looked up for relevant info.
1760 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001761 if target.endswith(".img"):
1762 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001763 mount_point = "/" + target
1764
Ying Wangf8824af2014-06-03 14:07:27 -07001765 fs_type = None
1766 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001767 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001768 if mount_point == "/userdata":
1769 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001770 p = info_dict["fstab"][mount_point]
1771 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001772 device = p.device
1773 if "/" in device:
1774 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001775 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001776 if not fs_type or not limit:
1777 return
Doug Zongkereef39442009-04-02 12:14:19 -07001778
Andrew Boie0f9aec82012-02-14 09:32:52 -08001779 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001780 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1781 # path.
1782 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1783 if size != limit:
1784 raise ExternalError(
1785 "Mismatching image size for %s: expected %d actual %d" % (
1786 target, limit, size))
1787 else:
1788 pct = float(size) * 100.0 / limit
1789 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1790 if pct >= 99.0:
1791 raise ExternalError(msg)
1792 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001793 logger.warning("\n WARNING: %s\n", msg)
1794 else:
1795 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001796
1797
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001798def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001799 """Parses the APK certs info from a given target-files zip.
1800
1801 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1802 tuple with the following elements: (1) a dictionary that maps packages to
1803 certs (based on the "certificate" and "private_key" attributes in the file;
1804 (2) a string representing the extension of compressed APKs in the target files
1805 (e.g ".gz", ".bro").
1806
1807 Args:
1808 tf_zip: The input target_files ZipFile (already open).
1809
1810 Returns:
1811 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1812 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1813 no compressed APKs.
1814 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001815 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001816 compressed_extension = None
1817
Tao Bao0f990332017-09-08 19:02:54 -07001818 # META/apkcerts.txt contains the info for _all_ the packages known at build
1819 # time. Filter out the ones that are not installed.
1820 installed_files = set()
1821 for name in tf_zip.namelist():
1822 basename = os.path.basename(name)
1823 if basename:
1824 installed_files.add(basename)
1825
Tao Baoda30cfa2017-12-01 16:19:46 -08001826 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001827 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001828 if not line:
1829 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001830 m = re.match(
1831 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07001832 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
1833 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08001834 line)
1835 if not m:
1836 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001837
Tao Bao818ddf52018-01-05 11:17:34 -08001838 matches = m.groupdict()
1839 cert = matches["CERT"]
1840 privkey = matches["PRIVKEY"]
1841 name = matches["NAME"]
1842 this_compressed_extension = matches["COMPRESSED"]
1843
1844 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1845 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1846 if cert in SPECIAL_CERT_STRINGS and not privkey:
1847 certmap[name] = cert
1848 elif (cert.endswith(OPTIONS.public_key_suffix) and
1849 privkey.endswith(OPTIONS.private_key_suffix) and
1850 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1851 certmap[name] = cert[:-public_key_suffix_len]
1852 else:
1853 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1854
1855 if not this_compressed_extension:
1856 continue
1857
1858 # Only count the installed files.
1859 filename = name + '.' + this_compressed_extension
1860 if filename not in installed_files:
1861 continue
1862
1863 # Make sure that all the values in the compression map have the same
1864 # extension. We don't support multiple compression methods in the same
1865 # system image.
1866 if compressed_extension:
1867 if this_compressed_extension != compressed_extension:
1868 raise ValueError(
1869 "Multiple compressed extensions: {} vs {}".format(
1870 compressed_extension, this_compressed_extension))
1871 else:
1872 compressed_extension = this_compressed_extension
1873
1874 return (certmap,
1875 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001876
1877
Doug Zongkereef39442009-04-02 12:14:19 -07001878COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001879Global options
1880
1881 -p (--path) <dir>
1882 Prepend <dir>/bin to the list of places to search for binaries run by this
1883 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001884
Doug Zongker05d3dea2009-06-22 11:32:31 -07001885 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001886 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001887
Tao Bao30df8b42018-04-23 15:32:53 -07001888 -x (--extra) <key=value>
1889 Add a key/value pair to the 'extras' dict, which device-specific extension
1890 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001891
Doug Zongkereef39442009-04-02 12:14:19 -07001892 -v (--verbose)
1893 Show command lines being executed.
1894
1895 -h (--help)
1896 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001897
1898 --logfile <file>
1899 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001900"""
1901
1902def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001903 print(docstring.rstrip("\n"))
1904 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001905
1906
1907def ParseOptions(argv,
1908 docstring,
1909 extra_opts="", extra_long_opts=(),
1910 extra_option_handler=None):
1911 """Parse the options in argv and return any arguments that aren't
1912 flags. docstring is the calling module's docstring, to be displayed
1913 for errors and -h. extra_opts and extra_long_opts are for flags
1914 defined by the caller, which are processed by passing them to
1915 extra_option_handler."""
1916
1917 try:
1918 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001919 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001920 ["help", "verbose", "path=", "signapk_path=",
1921 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08001922 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001923 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1924 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07001925 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
1926 "aftl_key_path=", "aftl_manufacturer_key_path=",
1927 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001928 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001929 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001930 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001931 sys.exit(2)
1932
Doug Zongkereef39442009-04-02 12:14:19 -07001933 for o, a in opts:
1934 if o in ("-h", "--help"):
1935 Usage(docstring)
1936 sys.exit()
1937 elif o in ("-v", "--verbose"):
1938 OPTIONS.verbose = True
1939 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001940 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001941 elif o in ("--signapk_path",):
1942 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001943 elif o in ("--signapk_shared_library_path",):
1944 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001945 elif o in ("--extra_signapk_args",):
1946 OPTIONS.extra_signapk_args = shlex.split(a)
1947 elif o in ("--java_path",):
1948 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001949 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001950 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08001951 elif o in ("--android_jar_path",):
1952 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001953 elif o in ("--public_key_suffix",):
1954 OPTIONS.public_key_suffix = a
1955 elif o in ("--private_key_suffix",):
1956 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001957 elif o in ("--boot_signer_path",):
1958 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001959 elif o in ("--boot_signer_args",):
1960 OPTIONS.boot_signer_args = shlex.split(a)
1961 elif o in ("--verity_signer_path",):
1962 OPTIONS.verity_signer_path = a
1963 elif o in ("--verity_signer_args",):
1964 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07001965 elif o in ("--aftl_tool_path",):
1966 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08001967 elif o in ("--aftl_server",):
1968 OPTIONS.aftl_server = a
1969 elif o in ("--aftl_key_path",):
1970 OPTIONS.aftl_key_path = a
1971 elif o in ("--aftl_manufacturer_key_path",):
1972 OPTIONS.aftl_manufacturer_key_path = a
1973 elif o in ("--aftl_signer_helper",):
1974 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07001975 elif o in ("-s", "--device_specific"):
1976 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001977 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001978 key, value = a.split("=", 1)
1979 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001980 elif o in ("--logfile",):
1981 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001982 else:
1983 if extra_option_handler is None or not extra_option_handler(o, a):
1984 assert False, "unknown option \"%s\"" % (o,)
1985
Doug Zongker85448772014-09-09 14:59:20 -07001986 if OPTIONS.search_path:
1987 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1988 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001989
1990 return args
1991
1992
Tao Bao4c851b12016-09-19 13:54:38 -07001993def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001994 """Make a temp file and add it to the list of things to be deleted
1995 when Cleanup() is called. Return the filename."""
1996 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1997 os.close(fd)
1998 OPTIONS.tempfiles.append(fn)
1999 return fn
2000
2001
Tao Bao1c830bf2017-12-25 10:43:47 -08002002def MakeTempDir(prefix='tmp', suffix=''):
2003 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2004
2005 Returns:
2006 The absolute pathname of the new directory.
2007 """
2008 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2009 OPTIONS.tempfiles.append(dir_name)
2010 return dir_name
2011
2012
Doug Zongkereef39442009-04-02 12:14:19 -07002013def Cleanup():
2014 for i in OPTIONS.tempfiles:
2015 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002016 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002017 else:
2018 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002019 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002020
2021
2022class PasswordManager(object):
2023 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002024 self.editor = os.getenv("EDITOR")
2025 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002026
2027 def GetPasswords(self, items):
2028 """Get passwords corresponding to each string in 'items',
2029 returning a dict. (The dict may have keys in addition to the
2030 values in 'items'.)
2031
2032 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2033 user edit that file to add more needed passwords. If no editor is
2034 available, or $ANDROID_PW_FILE isn't define, prompts the user
2035 interactively in the ordinary way.
2036 """
2037
2038 current = self.ReadFile()
2039
2040 first = True
2041 while True:
2042 missing = []
2043 for i in items:
2044 if i not in current or not current[i]:
2045 missing.append(i)
2046 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002047 if not missing:
2048 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002049
2050 for i in missing:
2051 current[i] = ""
2052
2053 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002054 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002055 if sys.version_info[0] >= 3:
2056 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002057 answer = raw_input("try to edit again? [y]> ").strip()
2058 if answer and answer[0] not in 'yY':
2059 raise RuntimeError("key passwords unavailable")
2060 first = False
2061
2062 current = self.UpdateAndReadFile(current)
2063
Dan Albert8b72aef2015-03-23 19:13:21 -07002064 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002065 """Prompt the user to enter a value (password) for each key in
2066 'current' whose value is fales. Returns a new dict with all the
2067 values.
2068 """
2069 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002070 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002071 if v:
2072 result[k] = v
2073 else:
2074 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002075 result[k] = getpass.getpass(
2076 "Enter password for %s key> " % k).strip()
2077 if result[k]:
2078 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002079 return result
2080
2081 def UpdateAndReadFile(self, current):
2082 if not self.editor or not self.pwfile:
2083 return self.PromptResult(current)
2084
2085 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002086 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002087 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2088 f.write("# (Additional spaces are harmless.)\n\n")
2089
2090 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002091 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002092 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002093 f.write("[[[ %s ]]] %s\n" % (v, k))
2094 if not v and first_line is None:
2095 # position cursor on first line with no password.
2096 first_line = i + 4
2097 f.close()
2098
Tao Bao986ee862018-10-04 15:46:16 -07002099 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002100
2101 return self.ReadFile()
2102
2103 def ReadFile(self):
2104 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002105 if self.pwfile is None:
2106 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002107 try:
2108 f = open(self.pwfile, "r")
2109 for line in f:
2110 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002111 if not line or line[0] == '#':
2112 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002113 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2114 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002115 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002116 else:
2117 result[m.group(2)] = m.group(1)
2118 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002119 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002120 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002121 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002122 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002123
2124
Dan Albert8e0178d2015-01-27 15:53:15 -08002125def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2126 compress_type=None):
2127 import datetime
2128
2129 # http://b/18015246
2130 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2131 # for files larger than 2GiB. We can work around this by adjusting their
2132 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2133 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2134 # it isn't clear to me exactly what circumstances cause this).
2135 # `zipfile.write()` must be used directly to work around this.
2136 #
2137 # This mess can be avoided if we port to python3.
2138 saved_zip64_limit = zipfile.ZIP64_LIMIT
2139 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2140
2141 if compress_type is None:
2142 compress_type = zip_file.compression
2143 if arcname is None:
2144 arcname = filename
2145
2146 saved_stat = os.stat(filename)
2147
2148 try:
2149 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2150 # file to be zipped and reset it when we're done.
2151 os.chmod(filename, perms)
2152
2153 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002154 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2155 # intentional. zip stores datetimes in local time without a time zone
2156 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2157 # in the zip archive.
2158 local_epoch = datetime.datetime.fromtimestamp(0)
2159 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002160 os.utime(filename, (timestamp, timestamp))
2161
2162 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2163 finally:
2164 os.chmod(filename, saved_stat.st_mode)
2165 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2166 zipfile.ZIP64_LIMIT = saved_zip64_limit
2167
2168
Tao Bao58c1b962015-05-20 09:32:18 -07002169def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002170 compress_type=None):
2171 """Wrap zipfile.writestr() function to work around the zip64 limit.
2172
2173 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2174 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2175 when calling crc32(bytes).
2176
2177 But it still works fine to write a shorter string into a large zip file.
2178 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2179 when we know the string won't be too long.
2180 """
2181
2182 saved_zip64_limit = zipfile.ZIP64_LIMIT
2183 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2184
2185 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2186 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002187 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002188 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002189 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002190 else:
Tao Baof3282b42015-04-01 11:21:55 -07002191 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002192 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2193 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2194 # such a case (since
2195 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2196 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2197 # permission bits. We follow the logic in Python 3 to get consistent
2198 # behavior between using the two versions.
2199 if not zinfo.external_attr:
2200 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002201
2202 # If compress_type is given, it overrides the value in zinfo.
2203 if compress_type is not None:
2204 zinfo.compress_type = compress_type
2205
Tao Bao58c1b962015-05-20 09:32:18 -07002206 # If perms is given, it has a priority.
2207 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002208 # If perms doesn't set the file type, mark it as a regular file.
2209 if perms & 0o770000 == 0:
2210 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002211 zinfo.external_attr = perms << 16
2212
Tao Baof3282b42015-04-01 11:21:55 -07002213 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002214 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2215
Dan Albert8b72aef2015-03-23 19:13:21 -07002216 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002217 zipfile.ZIP64_LIMIT = saved_zip64_limit
2218
2219
Tao Bao89d7ab22017-12-14 17:05:33 -08002220def ZipDelete(zip_filename, entries):
2221 """Deletes entries from a ZIP file.
2222
2223 Since deleting entries from a ZIP file is not supported, it shells out to
2224 'zip -d'.
2225
2226 Args:
2227 zip_filename: The name of the ZIP file.
2228 entries: The name of the entry, or the list of names to be deleted.
2229
2230 Raises:
2231 AssertionError: In case of non-zero return from 'zip'.
2232 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002233 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002234 entries = [entries]
2235 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002236 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002237
2238
Tao Baof3282b42015-04-01 11:21:55 -07002239def ZipClose(zip_file):
2240 # http://b/18015246
2241 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2242 # central directory.
2243 saved_zip64_limit = zipfile.ZIP64_LIMIT
2244 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2245
2246 zip_file.close()
2247
2248 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002249
2250
2251class DeviceSpecificParams(object):
2252 module = None
2253 def __init__(self, **kwargs):
2254 """Keyword arguments to the constructor become attributes of this
2255 object, which is passed to all functions in the device-specific
2256 module."""
Tao Bao38884282019-07-10 22:20:56 -07002257 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002258 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002259 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002260
2261 if self.module is None:
2262 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002263 if not path:
2264 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002265 try:
2266 if os.path.isdir(path):
2267 info = imp.find_module("releasetools", [path])
2268 else:
2269 d, f = os.path.split(path)
2270 b, x = os.path.splitext(f)
2271 if x == ".py":
2272 f = b
2273 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002274 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002275 self.module = imp.load_module("device_specific", *info)
2276 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002277 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002278
2279 def _DoCall(self, function_name, *args, **kwargs):
2280 """Call the named function in the device-specific module, passing
2281 the given args and kwargs. The first argument to the call will be
2282 the DeviceSpecific object itself. If there is no module, or the
2283 module does not define the function, return the value of the
2284 'default' kwarg (which itself defaults to None)."""
2285 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002286 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002287 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2288
2289 def FullOTA_Assertions(self):
2290 """Called after emitting the block of assertions at the top of a
2291 full OTA package. Implementations can add whatever additional
2292 assertions they like."""
2293 return self._DoCall("FullOTA_Assertions")
2294
Doug Zongkere5ff5902012-01-17 10:55:37 -08002295 def FullOTA_InstallBegin(self):
2296 """Called at the start of full OTA installation."""
2297 return self._DoCall("FullOTA_InstallBegin")
2298
Yifan Hong10c530d2018-12-27 17:34:18 -08002299 def FullOTA_GetBlockDifferences(self):
2300 """Called during full OTA installation and verification.
2301 Implementation should return a list of BlockDifference objects describing
2302 the update on each additional partitions.
2303 """
2304 return self._DoCall("FullOTA_GetBlockDifferences")
2305
Doug Zongker05d3dea2009-06-22 11:32:31 -07002306 def FullOTA_InstallEnd(self):
2307 """Called at the end of full OTA installation; typically this is
2308 used to install the image for the device's baseband processor."""
2309 return self._DoCall("FullOTA_InstallEnd")
2310
2311 def IncrementalOTA_Assertions(self):
2312 """Called after emitting the block of assertions at the top of an
2313 incremental OTA package. Implementations can add whatever
2314 additional assertions they like."""
2315 return self._DoCall("IncrementalOTA_Assertions")
2316
Doug Zongkere5ff5902012-01-17 10:55:37 -08002317 def IncrementalOTA_VerifyBegin(self):
2318 """Called at the start of the verification phase of incremental
2319 OTA installation; additional checks can be placed here to abort
2320 the script before any changes are made."""
2321 return self._DoCall("IncrementalOTA_VerifyBegin")
2322
Doug Zongker05d3dea2009-06-22 11:32:31 -07002323 def IncrementalOTA_VerifyEnd(self):
2324 """Called at the end of the verification phase of incremental OTA
2325 installation; additional checks can be placed here to abort the
2326 script before any changes are made."""
2327 return self._DoCall("IncrementalOTA_VerifyEnd")
2328
Doug Zongkere5ff5902012-01-17 10:55:37 -08002329 def IncrementalOTA_InstallBegin(self):
2330 """Called at the start of incremental OTA installation (after
2331 verification is complete)."""
2332 return self._DoCall("IncrementalOTA_InstallBegin")
2333
Yifan Hong10c530d2018-12-27 17:34:18 -08002334 def IncrementalOTA_GetBlockDifferences(self):
2335 """Called during incremental OTA installation and verification.
2336 Implementation should return a list of BlockDifference objects describing
2337 the update on each additional partitions.
2338 """
2339 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2340
Doug Zongker05d3dea2009-06-22 11:32:31 -07002341 def IncrementalOTA_InstallEnd(self):
2342 """Called at the end of incremental OTA installation; typically
2343 this is used to install the image for the device's baseband
2344 processor."""
2345 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002346
Tao Bao9bc6bb22015-11-09 16:58:28 -08002347 def VerifyOTA_Assertions(self):
2348 return self._DoCall("VerifyOTA_Assertions")
2349
Tao Bao76def242017-11-21 09:25:31 -08002350
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002351class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002352 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002353 self.name = name
2354 self.data = data
2355 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002356 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002357 self.sha1 = sha1(data).hexdigest()
2358
2359 @classmethod
2360 def FromLocalFile(cls, name, diskname):
2361 f = open(diskname, "rb")
2362 data = f.read()
2363 f.close()
2364 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002365
2366 def WriteToTemp(self):
2367 t = tempfile.NamedTemporaryFile()
2368 t.write(self.data)
2369 t.flush()
2370 return t
2371
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002372 def WriteToDir(self, d):
2373 with open(os.path.join(d, self.name), "wb") as fp:
2374 fp.write(self.data)
2375
Geremy Condra36bd3652014-02-06 19:45:10 -08002376 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002377 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002378
Tao Bao76def242017-11-21 09:25:31 -08002379
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002380DIFF_PROGRAM_BY_EXT = {
2381 ".gz" : "imgdiff",
2382 ".zip" : ["imgdiff", "-z"],
2383 ".jar" : ["imgdiff", "-z"],
2384 ".apk" : ["imgdiff", "-z"],
2385 ".img" : "imgdiff",
2386 }
2387
Tao Bao76def242017-11-21 09:25:31 -08002388
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002389class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002390 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002391 self.tf = tf
2392 self.sf = sf
2393 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002394 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002395
2396 def ComputePatch(self):
2397 """Compute the patch (as a string of data) needed to turn sf into
2398 tf. Returns the same tuple as GetPatch()."""
2399
2400 tf = self.tf
2401 sf = self.sf
2402
Doug Zongker24cd2802012-08-14 16:36:15 -07002403 if self.diff_program:
2404 diff_program = self.diff_program
2405 else:
2406 ext = os.path.splitext(tf.name)[1]
2407 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002408
2409 ttemp = tf.WriteToTemp()
2410 stemp = sf.WriteToTemp()
2411
2412 ext = os.path.splitext(tf.name)[1]
2413
2414 try:
2415 ptemp = tempfile.NamedTemporaryFile()
2416 if isinstance(diff_program, list):
2417 cmd = copy.copy(diff_program)
2418 else:
2419 cmd = [diff_program]
2420 cmd.append(stemp.name)
2421 cmd.append(ttemp.name)
2422 cmd.append(ptemp.name)
2423 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002424 err = []
2425 def run():
2426 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002427 if e:
2428 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002429 th = threading.Thread(target=run)
2430 th.start()
2431 th.join(timeout=300) # 5 mins
2432 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002433 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002434 p.terminate()
2435 th.join(5)
2436 if th.is_alive():
2437 p.kill()
2438 th.join()
2439
Tianjie Xua2a9f992018-01-05 15:15:54 -08002440 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002441 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002442 self.patch = None
2443 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002444 diff = ptemp.read()
2445 finally:
2446 ptemp.close()
2447 stemp.close()
2448 ttemp.close()
2449
2450 self.patch = diff
2451 return self.tf, self.sf, self.patch
2452
2453
2454 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002455 """Returns a tuple of (target_file, source_file, patch_data).
2456
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002457 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002458 computing the patch failed.
2459 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002460 return self.tf, self.sf, self.patch
2461
2462
2463def ComputeDifferences(diffs):
2464 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002465 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002466
2467 # Do the largest files first, to try and reduce the long-pole effect.
2468 by_size = [(i.tf.size, i) for i in diffs]
2469 by_size.sort(reverse=True)
2470 by_size = [i[1] for i in by_size]
2471
2472 lock = threading.Lock()
2473 diff_iter = iter(by_size) # accessed under lock
2474
2475 def worker():
2476 try:
2477 lock.acquire()
2478 for d in diff_iter:
2479 lock.release()
2480 start = time.time()
2481 d.ComputePatch()
2482 dur = time.time() - start
2483 lock.acquire()
2484
2485 tf, sf, patch = d.GetPatch()
2486 if sf.name == tf.name:
2487 name = tf.name
2488 else:
2489 name = "%s (%s)" % (tf.name, sf.name)
2490 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002491 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002492 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002493 logger.info(
2494 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2495 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002496 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002497 except Exception:
2498 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002499 raise
2500
2501 # start worker threads; wait for them all to finish.
2502 threads = [threading.Thread(target=worker)
2503 for i in range(OPTIONS.worker_threads)]
2504 for th in threads:
2505 th.start()
2506 while threads:
2507 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002508
2509
Dan Albert8b72aef2015-03-23 19:13:21 -07002510class BlockDifference(object):
2511 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002512 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002513 self.tgt = tgt
2514 self.src = src
2515 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002516 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002517 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002518
Tao Baodd2a5892015-03-12 12:32:37 -07002519 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002520 version = max(
2521 int(i) for i in
2522 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002523 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002524 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002525
Tianjie Xu41976c72019-07-03 13:57:01 -07002526 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2527 version=self.version,
2528 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002529 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002530 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002531 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002532 self.touched_src_ranges = b.touched_src_ranges
2533 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002534
Yifan Hong10c530d2018-12-27 17:34:18 -08002535 # On devices with dynamic partitions, for new partitions,
2536 # src is None but OPTIONS.source_info_dict is not.
2537 if OPTIONS.source_info_dict is None:
2538 is_dynamic_build = OPTIONS.info_dict.get(
2539 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002540 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002541 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002542 is_dynamic_build = OPTIONS.source_info_dict.get(
2543 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002544 is_dynamic_source = partition in shlex.split(
2545 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002546
Yifan Hongbb2658d2019-01-25 12:30:58 -08002547 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002548 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2549
Yifan Hongbb2658d2019-01-25 12:30:58 -08002550 # For dynamic partitions builds, check partition list in both source
2551 # and target build because new partitions may be added, and existing
2552 # partitions may be removed.
2553 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2554
Yifan Hong10c530d2018-12-27 17:34:18 -08002555 if is_dynamic:
2556 self.device = 'map_partition("%s")' % partition
2557 else:
2558 if OPTIONS.source_info_dict is None:
2559 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2560 else:
2561 _, device_path = GetTypeAndDevice("/" + partition,
2562 OPTIONS.source_info_dict)
2563 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002564
Tao Baod8d14be2016-02-04 14:26:02 -08002565 @property
2566 def required_cache(self):
2567 return self._required_cache
2568
Tao Bao76def242017-11-21 09:25:31 -08002569 def WriteScript(self, script, output_zip, progress=None,
2570 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002571 if not self.src:
2572 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002573 script.Print("Patching %s image unconditionally..." % (self.partition,))
2574 else:
2575 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002576
Dan Albert8b72aef2015-03-23 19:13:21 -07002577 if progress:
2578 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002579 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002580
2581 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002582 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002583
Tao Bao9bc6bb22015-11-09 16:58:28 -08002584 def WriteStrictVerifyScript(self, script):
2585 """Verify all the blocks in the care_map, including clobbered blocks.
2586
2587 This differs from the WriteVerifyScript() function: a) it prints different
2588 error messages; b) it doesn't allow half-way updated images to pass the
2589 verification."""
2590
2591 partition = self.partition
2592 script.Print("Verifying %s..." % (partition,))
2593 ranges = self.tgt.care_map
2594 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002595 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002596 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2597 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002598 self.device, ranges_str,
2599 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002600 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002601 script.AppendExtra("")
2602
Tao Baod522bdc2016-04-12 15:53:16 -07002603 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002604 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002605
2606 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002607 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002608 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002609
2610 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002611 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002612 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002613 ranges = self.touched_src_ranges
2614 expected_sha1 = self.touched_src_sha1
2615 else:
2616 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2617 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002618
2619 # No blocks to be checked, skipping.
2620 if not ranges:
2621 return
2622
Tao Bao5ece99d2015-05-12 11:42:31 -07002623 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002624 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002625 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002626 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2627 '"%s.patch.dat")) then' % (
2628 self.device, ranges_str, expected_sha1,
2629 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002630 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002631 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002632
Tianjie Xufc3422a2015-12-15 11:53:59 -08002633 if self.version >= 4:
2634
2635 # Bug: 21124327
2636 # When generating incrementals for the system and vendor partitions in
2637 # version 4 or newer, explicitly check the first block (which contains
2638 # the superblock) of the partition to see if it's what we expect. If
2639 # this check fails, give an explicit log message about the partition
2640 # having been remounted R/W (the most likely explanation).
2641 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002642 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002643
2644 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002645 if partition == "system":
2646 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2647 else:
2648 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002649 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002650 'ifelse (block_image_recover({device}, "{ranges}") && '
2651 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002652 'package_extract_file("{partition}.transfer.list"), '
2653 '"{partition}.new.dat", "{partition}.patch.dat"), '
2654 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002655 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002656 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002657 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002658
Tao Baodd2a5892015-03-12 12:32:37 -07002659 # Abort the OTA update. Note that the incremental OTA cannot be applied
2660 # even if it may match the checksum of the target partition.
2661 # a) If version < 3, operations like move and erase will make changes
2662 # unconditionally and damage the partition.
2663 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002664 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002665 if partition == "system":
2666 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2667 else:
2668 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2669 script.AppendExtra((
2670 'abort("E%d: %s partition has unexpected contents");\n'
2671 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002672
Yifan Hong10c530d2018-12-27 17:34:18 -08002673 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002674 partition = self.partition
2675 script.Print('Verifying the updated %s image...' % (partition,))
2676 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2677 ranges = self.tgt.care_map
2678 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002679 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002680 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002681 self.device, ranges_str,
2682 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002683
2684 # Bug: 20881595
2685 # Verify that extended blocks are really zeroed out.
2686 if self.tgt.extended:
2687 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002688 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002689 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002690 self.device, ranges_str,
2691 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002692 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002693 if partition == "system":
2694 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2695 else:
2696 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002697 script.AppendExtra(
2698 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002699 ' abort("E%d: %s partition has unexpected non-zero contents after '
2700 'OTA update");\n'
2701 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002702 else:
2703 script.Print('Verified the updated %s image.' % (partition,))
2704
Tianjie Xu209db462016-05-24 17:34:52 -07002705 if partition == "system":
2706 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2707 else:
2708 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2709
Tao Bao5fcaaef2015-06-01 13:40:49 -07002710 script.AppendExtra(
2711 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002712 ' abort("E%d: %s partition has unexpected contents after OTA '
2713 'update");\n'
2714 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002715
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002716 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002717 ZipWrite(output_zip,
2718 '{}.transfer.list'.format(self.path),
2719 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002720
Tao Bao76def242017-11-21 09:25:31 -08002721 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2722 # its size. Quailty 9 almost triples the compression time but doesn't
2723 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002724 # zip | brotli(quality 6) | brotli(quality 9)
2725 # compressed_size: 942M | 869M (~8% reduced) | 854M
2726 # compression_time: 75s | 265s | 719s
2727 # decompression_time: 15s | 25s | 25s
2728
2729 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002730 brotli_cmd = ['brotli', '--quality=6',
2731 '--output={}.new.dat.br'.format(self.path),
2732 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002733 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002734 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002735
2736 new_data_name = '{}.new.dat.br'.format(self.partition)
2737 ZipWrite(output_zip,
2738 '{}.new.dat.br'.format(self.path),
2739 new_data_name,
2740 compress_type=zipfile.ZIP_STORED)
2741 else:
2742 new_data_name = '{}.new.dat'.format(self.partition)
2743 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2744
Dan Albert8e0178d2015-01-27 15:53:15 -08002745 ZipWrite(output_zip,
2746 '{}.patch.dat'.format(self.path),
2747 '{}.patch.dat'.format(self.partition),
2748 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002749
Tianjie Xu209db462016-05-24 17:34:52 -07002750 if self.partition == "system":
2751 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2752 else:
2753 code = ErrorCode.VENDOR_UPDATE_FAILURE
2754
Yifan Hong10c530d2018-12-27 17:34:18 -08002755 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002756 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002757 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002758 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002759 device=self.device, partition=self.partition,
2760 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002761 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002762
Dan Albert8b72aef2015-03-23 19:13:21 -07002763 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002764 data = source.ReadRangeSet(ranges)
2765 ctx = sha1()
2766
2767 for p in data:
2768 ctx.update(p)
2769
2770 return ctx.hexdigest()
2771
Tao Baoe9b61912015-07-09 17:37:49 -07002772 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2773 """Return the hash value for all zero blocks."""
2774 zero_block = '\x00' * 4096
2775 ctx = sha1()
2776 for _ in range(num_blocks):
2777 ctx.update(zero_block)
2778
2779 return ctx.hexdigest()
2780
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002781
Tianjie Xu41976c72019-07-03 13:57:01 -07002782# Expose these two classes to support vendor-specific scripts
2783DataImage = images.DataImage
2784EmptyImage = images.EmptyImage
2785
Tao Bao76def242017-11-21 09:25:31 -08002786
Doug Zongker96a57e72010-09-26 14:57:41 -07002787# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002788PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002789 "ext4": "EMMC",
2790 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002791 "f2fs": "EMMC",
2792 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002793}
Doug Zongker96a57e72010-09-26 14:57:41 -07002794
Tao Bao76def242017-11-21 09:25:31 -08002795
Doug Zongker96a57e72010-09-26 14:57:41 -07002796def GetTypeAndDevice(mount_point, info):
2797 fstab = info["fstab"]
2798 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002799 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2800 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002801 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002802 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002803
2804
2805def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002806 """Parses and converts a PEM-encoded certificate into DER-encoded.
2807
2808 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2809
2810 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002811 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002812 """
2813 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002814 save = False
2815 for line in data.split("\n"):
2816 if "--END CERTIFICATE--" in line:
2817 break
2818 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002819 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002820 if "--BEGIN CERTIFICATE--" in line:
2821 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002822 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002823 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002824
Tao Bao04e1f012018-02-04 12:13:35 -08002825
2826def ExtractPublicKey(cert):
2827 """Extracts the public key (PEM-encoded) from the given certificate file.
2828
2829 Args:
2830 cert: The certificate filename.
2831
2832 Returns:
2833 The public key string.
2834
2835 Raises:
2836 AssertionError: On non-zero return from 'openssl'.
2837 """
2838 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2839 # While openssl 1.1 writes the key into the given filename followed by '-out',
2840 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2841 # stdout instead.
2842 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2843 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2844 pubkey, stderrdata = proc.communicate()
2845 assert proc.returncode == 0, \
2846 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2847 return pubkey
2848
2849
Tao Bao1ac886e2019-06-26 11:58:22 -07002850def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002851 """Extracts the AVB public key from the given public or private key.
2852
2853 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002854 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002855 key: The input key file, which should be PEM-encoded public or private key.
2856
2857 Returns:
2858 The path to the extracted AVB public key file.
2859 """
2860 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2861 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002862 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002863 return output
2864
2865
Doug Zongker412c02f2014-02-13 10:58:24 -08002866def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2867 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002868 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002869
Tao Bao6d5d6232018-03-09 17:04:42 -08002870 Most of the space in the boot and recovery images is just the kernel, which is
2871 identical for the two, so the resulting patch should be efficient. Add it to
2872 the output zip, along with a shell script that is run from init.rc on first
2873 boot to actually do the patching and install the new recovery image.
2874
2875 Args:
2876 input_dir: The top-level input directory of the target-files.zip.
2877 output_sink: The callback function that writes the result.
2878 recovery_img: File object for the recovery image.
2879 boot_img: File objects for the boot image.
2880 info_dict: A dict returned by common.LoadInfoDict() on the input
2881 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002882 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002883 if info_dict is None:
2884 info_dict = OPTIONS.info_dict
2885
Tao Bao6d5d6232018-03-09 17:04:42 -08002886 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002887 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2888
2889 if board_uses_vendorimage:
2890 # In this case, the output sink is rooted at VENDOR
2891 recovery_img_path = "etc/recovery.img"
2892 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2893 sh_dir = "bin"
2894 else:
2895 # In this case the output sink is rooted at SYSTEM
2896 recovery_img_path = "vendor/etc/recovery.img"
2897 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2898 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002899
Tao Baof2cffbd2015-07-22 12:33:18 -07002900 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002901 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002902
2903 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002904 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002905 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002906 # With system-root-image, boot and recovery images will have mismatching
2907 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2908 # to handle such a case.
2909 if system_root_image:
2910 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002911 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002912 assert not os.path.exists(path)
2913 else:
2914 diff_program = ["imgdiff"]
2915 if os.path.exists(path):
2916 diff_program.append("-b")
2917 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002918 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002919 else:
2920 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002921
2922 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2923 _, _, patch = d.ComputePatch()
2924 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002925
Dan Albertebb19aa2015-03-27 19:11:53 -07002926 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002927 # The following GetTypeAndDevice()s need to use the path in the target
2928 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002929 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2930 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2931 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002932 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002933
Tao Baof2cffbd2015-07-22 12:33:18 -07002934 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002935
2936 # Note that we use /vendor to refer to the recovery resources. This will
2937 # work for a separate vendor partition mounted at /vendor or a
2938 # /system/vendor subdirectory on the system partition, for which init will
2939 # create a symlink from /vendor to /system/vendor.
2940
2941 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002942if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2943 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002944 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002945 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2946 log -t recovery "Installing new recovery image: succeeded" || \\
2947 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002948else
2949 log -t recovery "Recovery image already installed"
2950fi
2951""" % {'type': recovery_type,
2952 'device': recovery_device,
2953 'sha1': recovery_img.sha1,
2954 'size': recovery_img.size}
2955 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002956 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002957if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2958 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002959 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002960 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2961 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2962 log -t recovery "Installing new recovery image: succeeded" || \\
2963 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002964else
2965 log -t recovery "Recovery image already installed"
2966fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002967""" % {'boot_size': boot_img.size,
2968 'boot_sha1': boot_img.sha1,
2969 'recovery_size': recovery_img.size,
2970 'recovery_sha1': recovery_img.sha1,
2971 'boot_type': boot_type,
2972 'boot_device': boot_device,
2973 'recovery_type': recovery_type,
2974 'recovery_device': recovery_device,
2975 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002976
Bill Peckhame868aec2019-09-17 17:06:47 -07002977 # The install script location moved from /system/etc to /system/bin in the L
2978 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2979 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002980
Tao Bao32fcdab2018-10-12 10:30:39 -07002981 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002982
Tao Baoda30cfa2017-12-01 16:19:46 -08002983 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002984
2985
2986class DynamicPartitionUpdate(object):
2987 def __init__(self, src_group=None, tgt_group=None, progress=None,
2988 block_difference=None):
2989 self.src_group = src_group
2990 self.tgt_group = tgt_group
2991 self.progress = progress
2992 self.block_difference = block_difference
2993
2994 @property
2995 def src_size(self):
2996 if not self.block_difference:
2997 return 0
2998 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2999
3000 @property
3001 def tgt_size(self):
3002 if not self.block_difference:
3003 return 0
3004 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3005
3006 @staticmethod
3007 def _GetSparseImageSize(img):
3008 if not img:
3009 return 0
3010 return img.blocksize * img.total_blocks
3011
3012
3013class DynamicGroupUpdate(object):
3014 def __init__(self, src_size=None, tgt_size=None):
3015 # None: group does not exist. 0: no size limits.
3016 self.src_size = src_size
3017 self.tgt_size = tgt_size
3018
3019
3020class DynamicPartitionsDifference(object):
3021 def __init__(self, info_dict, block_diffs, progress_dict=None,
3022 source_info_dict=None):
3023 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003024 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003025
3026 self._remove_all_before_apply = False
3027 if source_info_dict is None:
3028 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003029 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003030
Tao Baof1113e92019-06-18 12:10:14 -07003031 block_diff_dict = collections.OrderedDict(
3032 [(e.partition, e) for e in block_diffs])
3033
Yifan Hong10c530d2018-12-27 17:34:18 -08003034 assert len(block_diff_dict) == len(block_diffs), \
3035 "Duplicated BlockDifference object for {}".format(
3036 [partition for partition, count in
3037 collections.Counter(e.partition for e in block_diffs).items()
3038 if count > 1])
3039
Yifan Hong79997e52019-01-23 16:56:19 -08003040 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003041
3042 for p, block_diff in block_diff_dict.items():
3043 self._partition_updates[p] = DynamicPartitionUpdate()
3044 self._partition_updates[p].block_difference = block_diff
3045
3046 for p, progress in progress_dict.items():
3047 if p in self._partition_updates:
3048 self._partition_updates[p].progress = progress
3049
3050 tgt_groups = shlex.split(info_dict.get(
3051 "super_partition_groups", "").strip())
3052 src_groups = shlex.split(source_info_dict.get(
3053 "super_partition_groups", "").strip())
3054
3055 for g in tgt_groups:
3056 for p in shlex.split(info_dict.get(
3057 "super_%s_partition_list" % g, "").strip()):
3058 assert p in self._partition_updates, \
3059 "{} is in target super_{}_partition_list but no BlockDifference " \
3060 "object is provided.".format(p, g)
3061 self._partition_updates[p].tgt_group = g
3062
3063 for g in src_groups:
3064 for p in shlex.split(source_info_dict.get(
3065 "super_%s_partition_list" % g, "").strip()):
3066 assert p in self._partition_updates, \
3067 "{} is in source super_{}_partition_list but no BlockDifference " \
3068 "object is provided.".format(p, g)
3069 self._partition_updates[p].src_group = g
3070
Yifan Hong45433e42019-01-18 13:55:25 -08003071 target_dynamic_partitions = set(shlex.split(info_dict.get(
3072 "dynamic_partition_list", "").strip()))
3073 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3074 if u.tgt_size)
3075 assert block_diffs_with_target == target_dynamic_partitions, \
3076 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3077 list(target_dynamic_partitions), list(block_diffs_with_target))
3078
3079 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3080 "dynamic_partition_list", "").strip()))
3081 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3082 if u.src_size)
3083 assert block_diffs_with_source == source_dynamic_partitions, \
3084 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3085 list(source_dynamic_partitions), list(block_diffs_with_source))
3086
Yifan Hong10c530d2018-12-27 17:34:18 -08003087 if self._partition_updates:
3088 logger.info("Updating dynamic partitions %s",
3089 self._partition_updates.keys())
3090
Yifan Hong79997e52019-01-23 16:56:19 -08003091 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003092
3093 for g in tgt_groups:
3094 self._group_updates[g] = DynamicGroupUpdate()
3095 self._group_updates[g].tgt_size = int(info_dict.get(
3096 "super_%s_group_size" % g, "0").strip())
3097
3098 for g in src_groups:
3099 if g not in self._group_updates:
3100 self._group_updates[g] = DynamicGroupUpdate()
3101 self._group_updates[g].src_size = int(source_info_dict.get(
3102 "super_%s_group_size" % g, "0").strip())
3103
3104 self._Compute()
3105
3106 def WriteScript(self, script, output_zip, write_verify_script=False):
3107 script.Comment('--- Start patching dynamic partitions ---')
3108 for p, u in self._partition_updates.items():
3109 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3110 script.Comment('Patch partition %s' % p)
3111 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3112 write_verify_script=False)
3113
3114 op_list_path = MakeTempFile()
3115 with open(op_list_path, 'w') as f:
3116 for line in self._op_list:
3117 f.write('{}\n'.format(line))
3118
3119 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3120
3121 script.Comment('Update dynamic partition metadata')
3122 script.AppendExtra('assert(update_dynamic_partitions('
3123 'package_extract_file("dynamic_partitions_op_list")));')
3124
3125 if write_verify_script:
3126 for p, u in self._partition_updates.items():
3127 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3128 u.block_difference.WritePostInstallVerifyScript(script)
3129 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3130
3131 for p, u in self._partition_updates.items():
3132 if u.tgt_size and u.src_size <= u.tgt_size:
3133 script.Comment('Patch partition %s' % p)
3134 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3135 write_verify_script=write_verify_script)
3136 if write_verify_script:
3137 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3138
3139 script.Comment('--- End patching dynamic partitions ---')
3140
3141 def _Compute(self):
3142 self._op_list = list()
3143
3144 def append(line):
3145 self._op_list.append(line)
3146
3147 def comment(line):
3148 self._op_list.append("# %s" % line)
3149
3150 if self._remove_all_before_apply:
3151 comment('Remove all existing dynamic partitions and groups before '
3152 'applying full OTA')
3153 append('remove_all_groups')
3154
3155 for p, u in self._partition_updates.items():
3156 if u.src_group and not u.tgt_group:
3157 append('remove %s' % p)
3158
3159 for p, u in self._partition_updates.items():
3160 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3161 comment('Move partition %s from %s to default' % (p, u.src_group))
3162 append('move %s default' % p)
3163
3164 for p, u in self._partition_updates.items():
3165 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3166 comment('Shrink partition %s from %d to %d' %
3167 (p, u.src_size, u.tgt_size))
3168 append('resize %s %s' % (p, u.tgt_size))
3169
3170 for g, u in self._group_updates.items():
3171 if u.src_size is not None and u.tgt_size is None:
3172 append('remove_group %s' % g)
3173 if (u.src_size is not None and u.tgt_size is not None and
3174 u.src_size > u.tgt_size):
3175 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3176 append('resize_group %s %d' % (g, u.tgt_size))
3177
3178 for g, u in self._group_updates.items():
3179 if u.src_size is None and u.tgt_size is not None:
3180 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3181 append('add_group %s %d' % (g, u.tgt_size))
3182 if (u.src_size is not None and u.tgt_size is not None and
3183 u.src_size < u.tgt_size):
3184 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3185 append('resize_group %s %d' % (g, u.tgt_size))
3186
3187 for p, u in self._partition_updates.items():
3188 if u.tgt_group and not u.src_group:
3189 comment('Add partition %s to group %s' % (p, u.tgt_group))
3190 append('add %s %s' % (p, u.tgt_group))
3191
3192 for p, u in self._partition_updates.items():
3193 if u.tgt_size and u.src_size < u.tgt_size:
3194 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3195 append('resize %s %d' % (p, u.tgt_size))
3196
3197 for p, u in self._partition_updates.items():
3198 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3199 comment('Move partition %s from default to %s' %
3200 (p, u.tgt_group))
3201 append('move %s %s' % (p, u.tgt_group))