blob: bae0b20f21d3c2040dfbf46cd7f251cd2645d892 [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
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
112# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700114 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm',
115 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800116
Tao Bao08c190f2019-06-03 23:07:58 -0700117# Chained VBMeta partitions.
118AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
119
Tianjie Xu861f4132018-09-12 11:49:33 -0700120# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400121PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700122 'system',
123 'vendor',
124 'product',
125 'system_ext',
126 'odm',
127 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700128 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400129]
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
131
Tianjie Xu209db462016-05-24 17:34:52 -0700132class ErrorCode(object):
133 """Define error_codes for failures that happen during the actual
134 update package installation.
135
136 Error codes 0-999 are reserved for failures before the package
137 installation (i.e. low battery, package verification failure).
138 Detailed code in 'bootable/recovery/error_code.h' """
139
140 SYSTEM_VERIFICATION_FAILURE = 1000
141 SYSTEM_UPDATE_FAILURE = 1001
142 SYSTEM_UNEXPECTED_CONTENTS = 1002
143 SYSTEM_NONZERO_CONTENTS = 1003
144 SYSTEM_RECOVER_FAILURE = 1004
145 VENDOR_VERIFICATION_FAILURE = 2000
146 VENDOR_UPDATE_FAILURE = 2001
147 VENDOR_UNEXPECTED_CONTENTS = 2002
148 VENDOR_NONZERO_CONTENTS = 2003
149 VENDOR_RECOVER_FAILURE = 2004
150 OEM_PROP_MISMATCH = 3000
151 FINGERPRINT_MISMATCH = 3001
152 THUMBPRINT_MISMATCH = 3002
153 OLDER_BUILD = 3003
154 DEVICE_MISMATCH = 3004
155 BAD_PATCH_FILE = 3005
156 INSUFFICIENT_CACHE_SPACE = 3006
157 TUNE_PARTITION_FAILURE = 3007
158 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800159
Tao Bao80921982018-03-21 21:02:19 -0700160
Dan Albert8b72aef2015-03-23 19:13:21 -0700161class ExternalError(RuntimeError):
162 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700163
164
Tao Bao32fcdab2018-10-12 10:30:39 -0700165def InitLogging():
166 DEFAULT_LOGGING_CONFIG = {
167 'version': 1,
168 'disable_existing_loggers': False,
169 'formatters': {
170 'standard': {
171 'format':
172 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
173 'datefmt': '%Y-%m-%d %H:%M:%S',
174 },
175 },
176 'handlers': {
177 'default': {
178 'class': 'logging.StreamHandler',
179 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700180 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700181 },
182 },
183 'loggers': {
184 '': {
185 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700186 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700187 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700188 }
189 }
190 }
191 env_config = os.getenv('LOGGING_CONFIG')
192 if env_config:
193 with open(env_config) as f:
194 config = json.load(f)
195 else:
196 config = DEFAULT_LOGGING_CONFIG
197
198 # Increase the logging level for verbose mode.
199 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700200 config = copy.deepcopy(config)
201 config['handlers']['default']['level'] = 'INFO'
202
203 if OPTIONS.logfile:
204 config = copy.deepcopy(config)
205 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400206 'class': 'logging.FileHandler',
207 'formatter': 'standard',
208 'level': 'INFO',
209 'mode': 'w',
210 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700211 }
212 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700213
214 logging.config.dictConfig(config)
215
216
Yifan Hong8e332ff2020-07-29 17:51:55 -0700217def SetHostToolLocation(tool_name, location):
218 OPTIONS.host_tools[tool_name] = location
219
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900220def FindHostToolPath(tool_name):
221 """Finds the path to the host tool.
222
223 Args:
224 tool_name: name of the tool to find
225 Returns:
226 path to the tool if found under either one of the host_tools map or under
227 the same directory as this binary is located at. If not found, tool_name
228 is returned.
229 """
230 if tool_name in OPTIONS.host_tools:
231 return OPTIONS.host_tools[tool_name]
232
233 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
234 tool_path = os.path.join(my_dir, tool_name)
235 if os.path.exists(tool_path):
236 return tool_path
237
238 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700239
Tao Bao39451582017-05-04 11:10:47 -0700240def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700241 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700242
Tao Bao73dd4f42018-10-04 16:25:33 -0700243 Args:
244 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700245 verbose: Whether the commands should be shown. Default to the global
246 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700247 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
248 stdin, etc. stdout and stderr will default to subprocess.PIPE and
249 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800250 universal_newlines will default to True, as most of the users in
251 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700252
253 Returns:
254 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700255 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700256 if 'stdout' not in kwargs and 'stderr' not in kwargs:
257 kwargs['stdout'] = subprocess.PIPE
258 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800259 if 'universal_newlines' not in kwargs:
260 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700261
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900262 if args:
263 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700264 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900265 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700266
Tao Bao32fcdab2018-10-12 10:30:39 -0700267 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400268 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700269 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700270 return subprocess.Popen(args, **kwargs)
271
272
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800273def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800274 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800275
276 Args:
277 args: The command represented as a list of strings.
278 verbose: Whether the commands should be shown. Default to the global
279 verbosity if unspecified.
280 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
281 stdin, etc. stdout and stderr will default to subprocess.PIPE and
282 subprocess.STDOUT respectively unless caller specifies any of them.
283
Bill Peckham889b0c62019-02-21 18:53:37 -0800284 Raises:
285 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800286 """
287 proc = Run(args, verbose=verbose, **kwargs)
288 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800289
290 if proc.returncode != 0:
291 raise ExternalError(
292 "Failed to run command '{}' (exit code {})".format(
293 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800294
295
Tao Bao986ee862018-10-04 15:46:16 -0700296def RunAndCheckOutput(args, verbose=None, **kwargs):
297 """Runs the given command and returns the output.
298
299 Args:
300 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700301 verbose: Whether the commands should be shown. Default to the global
302 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700303 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
304 stdin, etc. stdout and stderr will default to subprocess.PIPE and
305 subprocess.STDOUT respectively unless caller specifies any of them.
306
307 Returns:
308 The output string.
309
310 Raises:
311 ExternalError: On non-zero exit from the command.
312 """
Tao Bao986ee862018-10-04 15:46:16 -0700313 proc = Run(args, verbose=verbose, **kwargs)
314 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800315 if output is None:
316 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700317 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400318 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700319 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700320 if proc.returncode != 0:
321 raise ExternalError(
322 "Failed to run command '{}' (exit code {}):\n{}".format(
323 args, proc.returncode, output))
324 return output
325
326
Tao Baoc765cca2018-01-31 17:32:40 -0800327def RoundUpTo4K(value):
328 rounded_up = value + 4095
329 return rounded_up - (rounded_up % 4096)
330
331
Ying Wang7e6d4e42010-12-13 16:25:36 -0800332def CloseInheritedPipes():
333 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
334 before doing other work."""
335 if platform.system() != "Darwin":
336 return
337 for d in range(3, 1025):
338 try:
339 stat = os.fstat(d)
340 if stat is not None:
341 pipebit = stat[0] & 0x1000
342 if pipebit != 0:
343 os.close(d)
344 except OSError:
345 pass
346
347
Tao Bao1c320f82019-10-04 23:25:12 -0700348class BuildInfo(object):
349 """A class that holds the information for a given build.
350
351 This class wraps up the property querying for a given source or target build.
352 It abstracts away the logic of handling OEM-specific properties, and caches
353 the commonly used properties such as fingerprint.
354
355 There are two types of info dicts: a) build-time info dict, which is generated
356 at build time (i.e. included in a target_files zip); b) OEM info dict that is
357 specified at package generation time (via command line argument
358 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
359 having "oem_fingerprint_properties" in build-time info dict), all the queries
360 would be answered based on build-time info dict only. Otherwise if using
361 OEM-specific properties, some of them will be calculated from two info dicts.
362
363 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800364 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700365
366 Attributes:
367 info_dict: The build-time info dict.
368 is_ab: Whether it's a build that uses A/B OTA.
369 oem_dicts: A list of OEM dicts.
370 oem_props: A list of OEM properties that should be read from OEM dicts; None
371 if the build doesn't use any OEM-specific property.
372 fingerprint: The fingerprint of the build, which would be calculated based
373 on OEM properties if applicable.
374 device: The device name, which could come from OEM dicts if applicable.
375 """
376
377 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
378 "ro.product.manufacturer", "ro.product.model",
379 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700380 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
381 "product", "odm", "vendor", "system_ext", "system"]
382 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
383 "product", "product_services", "odm", "vendor", "system"]
384 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700385
Tao Bao3ed35d32019-10-07 20:48:48 -0700386 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700387 """Initializes a BuildInfo instance with the given dicts.
388
389 Note that it only wraps up the given dicts, without making copies.
390
391 Arguments:
392 info_dict: The build-time info dict.
393 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
394 that it always uses the first dict to calculate the fingerprint or the
395 device name. The rest would be used for asserting OEM properties only
396 (e.g. one package can be installed on one of these devices).
397
398 Raises:
399 ValueError: On invalid inputs.
400 """
401 self.info_dict = info_dict
402 self.oem_dicts = oem_dicts
403
404 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700405
Hongguang Chend7c160f2020-05-03 21:24:26 -0700406 # Skip _oem_props if oem_dicts is None to use BuildInfo in
407 # sign_target_files_apks
408 if self.oem_dicts:
409 self._oem_props = info_dict.get("oem_fingerprint_properties")
410 else:
411 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700412
Daniel Normand5fe8622020-01-08 17:01:11 -0800413 def check_fingerprint(fingerprint):
414 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
415 raise ValueError(
416 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
417 "3.2.2. Build Parameters.".format(fingerprint))
418
Daniel Normand5fe8622020-01-08 17:01:11 -0800419 self._partition_fingerprints = {}
420 for partition in PARTITIONS_WITH_CARE_MAP:
421 try:
422 fingerprint = self.CalculatePartitionFingerprint(partition)
423 check_fingerprint(fingerprint)
424 self._partition_fingerprints[partition] = fingerprint
425 except ExternalError:
426 continue
427 if "system" in self._partition_fingerprints:
428 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
429 # need a fingerprint when creating the image.
430 self._partition_fingerprints[
431 "system_other"] = self._partition_fingerprints["system"]
432
Tao Bao1c320f82019-10-04 23:25:12 -0700433 # These two should be computed only after setting self._oem_props.
434 self._device = self.GetOemProperty("ro.product.device")
435 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800436 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700437
438 @property
439 def is_ab(self):
440 return self._is_ab
441
442 @property
443 def device(self):
444 return self._device
445
446 @property
447 def fingerprint(self):
448 return self._fingerprint
449
450 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700451 def oem_props(self):
452 return self._oem_props
453
454 def __getitem__(self, key):
455 return self.info_dict[key]
456
457 def __setitem__(self, key, value):
458 self.info_dict[key] = value
459
460 def get(self, key, default=None):
461 return self.info_dict.get(key, default)
462
463 def items(self):
464 return self.info_dict.items()
465
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000466 def _GetRawBuildProp(self, prop, partition):
467 prop_file = '{}.build.prop'.format(
468 partition) if partition else 'build.prop'
469 partition_props = self.info_dict.get(prop_file)
470 if not partition_props:
471 return None
472 return partition_props.GetProp(prop)
473
Daniel Normand5fe8622020-01-08 17:01:11 -0800474 def GetPartitionBuildProp(self, prop, partition):
475 """Returns the inquired build property for the provided partition."""
476 # If provided a partition for this property, only look within that
477 # partition's build.prop.
478 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
479 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
480 else:
481 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000482
483 prop_val = self._GetRawBuildProp(prop, partition)
484 if prop_val is not None:
485 return prop_val
486 raise ExternalError("couldn't find %s in %s.build.prop" %
487 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800488
Tao Bao1c320f82019-10-04 23:25:12 -0700489 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800490 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700491 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
492 return self._ResolveRoProductBuildProp(prop)
493
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000494 prop_val = self._GetRawBuildProp(prop, None)
495 if prop_val is not None:
496 return prop_val
497
498 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700499
500 def _ResolveRoProductBuildProp(self, prop):
501 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000502 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700503 if prop_val:
504 return prop_val
505
Steven Laver8e2086e2020-04-27 16:26:31 -0700506 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000507 source_order_val = self._GetRawBuildProp(
508 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700509 if source_order_val:
510 source_order = source_order_val.split(",")
511 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700512 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700513
514 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700515 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700516 raise ExternalError(
517 "Invalid ro.product.property_source_order '{}'".format(source_order))
518
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000519 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700520 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000521 "ro.product", "ro.product.{}".format(source_partition), 1)
522 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700523 if prop_val:
524 return prop_val
525
526 raise ExternalError("couldn't resolve {}".format(prop))
527
Steven Laver8e2086e2020-04-27 16:26:31 -0700528 def _GetRoProductPropsDefaultSourceOrder(self):
529 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
530 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000531 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700532 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000533 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700534 if android_version == "10":
535 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
536 # NOTE: float() conversion of android_version will have rounding error.
537 # We are checking for "9" or less, and using "< 10" is well outside of
538 # possible floating point rounding.
539 try:
540 android_version_val = float(android_version)
541 except ValueError:
542 android_version_val = 0
543 if android_version_val < 10:
544 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
545 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
546
Tianjieb37c5be2020-10-15 21:27:10 -0700547 def _GetPlatformVersion(self):
548 version_sdk = self.GetBuildProp("ro.build.version.sdk")
549 # init code switches to version_release_or_codename (see b/158483506). After
550 # API finalization, release_or_codename will be the same as release. This
551 # is the best effort to support pre-S dev stage builds.
552 if int(version_sdk) >= 30:
553 try:
554 return self.GetBuildProp("ro.build.version.release_or_codename")
555 except ExternalError:
556 logger.warning('Failed to find ro.build.version.release_or_codename')
557
558 return self.GetBuildProp("ro.build.version.release")
559
560 def _GetPartitionPlatformVersion(self, partition):
561 try:
562 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
563 partition)
564 except ExternalError:
565 return self.GetPartitionBuildProp("ro.build.version.release",
566 partition)
567
Tao Bao1c320f82019-10-04 23:25:12 -0700568 def GetOemProperty(self, key):
569 if self.oem_props is not None and key in self.oem_props:
570 return self.oem_dicts[0][key]
571 return self.GetBuildProp(key)
572
Daniel Normand5fe8622020-01-08 17:01:11 -0800573 def GetPartitionFingerprint(self, partition):
574 return self._partition_fingerprints.get(partition, None)
575
576 def CalculatePartitionFingerprint(self, partition):
577 try:
578 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
579 except ExternalError:
580 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
581 self.GetPartitionBuildProp("ro.product.brand", partition),
582 self.GetPartitionBuildProp("ro.product.name", partition),
583 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700584 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800585 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400586 self.GetPartitionBuildProp(
587 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800588 self.GetPartitionBuildProp("ro.build.type", partition),
589 self.GetPartitionBuildProp("ro.build.tags", partition))
590
Tao Bao1c320f82019-10-04 23:25:12 -0700591 def CalculateFingerprint(self):
592 if self.oem_props is None:
593 try:
594 return self.GetBuildProp("ro.build.fingerprint")
595 except ExternalError:
596 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
597 self.GetBuildProp("ro.product.brand"),
598 self.GetBuildProp("ro.product.name"),
599 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700600 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700601 self.GetBuildProp("ro.build.id"),
602 self.GetBuildProp("ro.build.version.incremental"),
603 self.GetBuildProp("ro.build.type"),
604 self.GetBuildProp("ro.build.tags"))
605 return "%s/%s/%s:%s" % (
606 self.GetOemProperty("ro.product.brand"),
607 self.GetOemProperty("ro.product.name"),
608 self.GetOemProperty("ro.product.device"),
609 self.GetBuildProp("ro.build.thumbprint"))
610
611 def WriteMountOemScript(self, script):
612 assert self.oem_props is not None
613 recovery_mount_options = self.info_dict.get("recovery_mount_options")
614 script.Mount("/oem", recovery_mount_options)
615
616 def WriteDeviceAssertions(self, script, oem_no_mount):
617 # Read the property directly if not using OEM properties.
618 if not self.oem_props:
619 script.AssertDevice(self.device)
620 return
621
622 # Otherwise assert OEM properties.
623 if not self.oem_dicts:
624 raise ExternalError(
625 "No OEM file provided to answer expected assertions")
626
627 for prop in self.oem_props.split():
628 values = []
629 for oem_dict in self.oem_dicts:
630 if prop in oem_dict:
631 values.append(oem_dict[prop])
632 if not values:
633 raise ExternalError(
634 "The OEM file is missing the property %s" % (prop,))
635 script.AssertOemProperty(prop, values, oem_no_mount)
636
637
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000638def ReadFromInputFile(input_file, fn):
639 """Reads the contents of fn from input zipfile or directory."""
640 if isinstance(input_file, zipfile.ZipFile):
641 return input_file.read(fn).decode()
642 else:
643 path = os.path.join(input_file, *fn.split("/"))
644 try:
645 with open(path) as f:
646 return f.read()
647 except IOError as e:
648 if e.errno == errno.ENOENT:
649 raise KeyError(fn)
650
651
Tao Bao410ad8b2018-08-24 12:08:38 -0700652def LoadInfoDict(input_file, repacking=False):
653 """Loads the key/value pairs from the given input target_files.
654
Tianjiea85bdf02020-07-29 11:56:19 -0700655 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700656 checks and returns the parsed key/value pairs for to the given build. It's
657 usually called early when working on input target_files files, e.g. when
658 generating OTAs, or signing builds. Note that the function may be called
659 against an old target_files file (i.e. from past dessert releases). So the
660 property parsing needs to be backward compatible.
661
662 In a `META/misc_info.txt`, a few properties are stored as links to the files
663 in the PRODUCT_OUT directory. It works fine with the build system. However,
664 they are no longer available when (re)generating images from target_files zip.
665 When `repacking` is True, redirect these properties to the actual files in the
666 unzipped directory.
667
668 Args:
669 input_file: The input target_files file, which could be an open
670 zipfile.ZipFile instance, or a str for the dir that contains the files
671 unzipped from a target_files file.
672 repacking: Whether it's trying repack an target_files file after loading the
673 info dict (default: False). If so, it will rewrite a few loaded
674 properties (e.g. selinux_fc, root_dir) to point to the actual files in
675 target_files file. When doing repacking, `input_file` must be a dir.
676
677 Returns:
678 A dict that contains the parsed key/value pairs.
679
680 Raises:
681 AssertionError: On invalid input arguments.
682 ValueError: On malformed input values.
683 """
684 if repacking:
685 assert isinstance(input_file, str), \
686 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700687
Doug Zongkerc9253822014-02-04 12:17:58 -0800688 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000689 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800690
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700691 try:
Michael Runge6e836112014-04-15 17:40:21 -0700692 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700693 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700694 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700695
Tao Bao410ad8b2018-08-24 12:08:38 -0700696 if "recovery_api_version" not in d:
697 raise ValueError("Failed to find 'recovery_api_version'")
698 if "fstab_version" not in d:
699 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800700
Tao Bao410ad8b2018-08-24 12:08:38 -0700701 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700702 # "selinux_fc" properties should point to the file_contexts files
703 # (file_contexts.bin) under META/.
704 for key in d:
705 if key.endswith("selinux_fc"):
706 fc_basename = os.path.basename(d[key])
707 fc_config = os.path.join(input_file, "META", fc_basename)
708 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700709
Daniel Norman72c626f2019-05-13 15:58:14 -0700710 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700711
Tom Cherryd14b8952018-08-09 14:26:00 -0700712 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700713 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700714 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700715 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700716
David Anderson0ec64ac2019-12-06 12:21:18 -0800717 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700718 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700719 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800720 key_name = part_name + "_base_fs_file"
721 if key_name not in d:
722 continue
723 basename = os.path.basename(d[key_name])
724 base_fs_file = os.path.join(input_file, "META", basename)
725 if os.path.exists(base_fs_file):
726 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700727 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700728 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800729 "Failed to find %s base fs file: %s", part_name, base_fs_file)
730 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700731
Doug Zongker37974732010-09-16 17:44:38 -0700732 def makeint(key):
733 if key in d:
734 d[key] = int(d[key], 0)
735
736 makeint("recovery_api_version")
737 makeint("blocksize")
738 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700739 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700740 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700741 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700742 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800743 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700744
Steve Muckle903a1ca2020-05-07 17:32:10 -0700745 boot_images = "boot.img"
746 if "boot_images" in d:
747 boot_images = d["boot_images"]
748 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400749 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700750
Tao Bao765668f2019-10-04 22:03:00 -0700751 # Load recovery fstab if applicable.
752 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800753
Tianjie Xu861f4132018-09-12 11:49:33 -0700754 # Tries to load the build props for all partitions with care_map, including
755 # system and vendor.
756 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800757 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000758 d[partition_prop] = PartitionBuildProps.FromInputFile(
759 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700760 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800761
Tao Bao3ed35d32019-10-07 20:48:48 -0700762 # Set up the salt (based on fingerprint) that will be used when adding AVB
763 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800764 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700765 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800766 for partition in PARTITIONS_WITH_CARE_MAP:
767 fingerprint = build_info.GetPartitionFingerprint(partition)
768 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400769 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400770 try:
771 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
772 except KeyError:
773 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700774 return d
775
Tao Baod1de6f32017-03-01 16:38:48 -0800776
Kelvin Zhang39aea442020-08-17 11:04:25 -0400777
Daniel Norman4cc9df62019-07-18 10:11:07 -0700778def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900779 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700780 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900781
Daniel Norman4cc9df62019-07-18 10:11:07 -0700782
783def LoadDictionaryFromFile(file_path):
784 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900785 return LoadDictionaryFromLines(lines)
786
787
Michael Runge6e836112014-04-15 17:40:21 -0700788def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700789 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700790 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700791 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700792 if not line or line.startswith("#"):
793 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700794 if "=" in line:
795 name, value = line.split("=", 1)
796 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700797 return d
798
Tao Baod1de6f32017-03-01 16:38:48 -0800799
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000800class PartitionBuildProps(object):
801 """The class holds the build prop of a particular partition.
802
803 This class loads the build.prop and holds the build properties for a given
804 partition. It also partially recognizes the 'import' statement in the
805 build.prop; and calculates alternative values of some specific build
806 properties during runtime.
807
808 Attributes:
809 input_file: a zipped target-file or an unzipped target-file directory.
810 partition: name of the partition.
811 props_allow_override: a list of build properties to search for the
812 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000813 build_props: a dict of build properties for the given partition.
814 prop_overrides: a set of props that are overridden by import.
815 placeholder_values: A dict of runtime variables' values to replace the
816 placeholders in the build.prop file. We expect exactly one value for
817 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000818 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400819
Tianjie Xu9afb2212020-05-10 21:48:15 +0000820 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000821 self.input_file = input_file
822 self.partition = name
823 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000824 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000825 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000826 self.prop_overrides = set()
827 self.placeholder_values = {}
828 if placeholder_values:
829 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000830
831 @staticmethod
832 def FromDictionary(name, build_props):
833 """Constructs an instance from a build prop dictionary."""
834
835 props = PartitionBuildProps("unknown", name)
836 props.build_props = build_props.copy()
837 return props
838
839 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000840 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000841 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000842 data = ''
843 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
844 '{}/build.prop'.format(name.upper())]:
845 try:
846 data = ReadFromInputFile(input_file, prop_file)
847 break
848 except KeyError:
849 logger.warning('Failed to read %s', prop_file)
850
Tianjie Xu9afb2212020-05-10 21:48:15 +0000851 props = PartitionBuildProps(input_file, name, placeholder_values)
852 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000853 return props
854
Yifan Hong125d0b62020-09-24 17:07:03 -0700855 @staticmethod
856 def FromBuildPropFile(name, build_prop_file):
857 """Constructs an instance from a build prop file."""
858
859 props = PartitionBuildProps("unknown", name)
860 with open(build_prop_file) as f:
861 props._LoadBuildProp(f.read())
862 return props
863
Tianjie Xu9afb2212020-05-10 21:48:15 +0000864 def _LoadBuildProp(self, data):
865 for line in data.split('\n'):
866 line = line.strip()
867 if not line or line.startswith("#"):
868 continue
869 if line.startswith("import"):
870 overrides = self._ImportParser(line)
871 duplicates = self.prop_overrides.intersection(overrides.keys())
872 if duplicates:
873 raise ValueError('prop {} is overridden multiple times'.format(
874 ','.join(duplicates)))
875 self.prop_overrides = self.prop_overrides.union(overrides.keys())
876 self.build_props.update(overrides)
877 elif "=" in line:
878 name, value = line.split("=", 1)
879 if name in self.prop_overrides:
880 raise ValueError('prop {} is set again after overridden by import '
881 'statement'.format(name))
882 self.build_props[name] = value
883
884 def _ImportParser(self, line):
885 """Parses the build prop in a given import statement."""
886
887 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400888 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000889 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700890
891 if len(tokens) == 3:
892 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
893 return {}
894
Tianjie Xu9afb2212020-05-10 21:48:15 +0000895 import_path = tokens[1]
896 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
897 raise ValueError('Unrecognized import path {}'.format(line))
898
899 # We only recognize a subset of import statement that the init process
900 # supports. And we can loose the restriction based on how the dynamic
901 # fingerprint is used in practice. The placeholder format should be
902 # ${placeholder}, and its value should be provided by the caller through
903 # the placeholder_values.
904 for prop, value in self.placeholder_values.items():
905 prop_place_holder = '${{{}}}'.format(prop)
906 if prop_place_holder in import_path:
907 import_path = import_path.replace(prop_place_holder, value)
908 if '$' in import_path:
909 logger.info('Unresolved place holder in import path %s', import_path)
910 return {}
911
912 import_path = import_path.replace('/{}'.format(self.partition),
913 self.partition.upper())
914 logger.info('Parsing build props override from %s', import_path)
915
916 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
917 d = LoadDictionaryFromLines(lines)
918 return {key: val for key, val in d.items()
919 if key in self.props_allow_override}
920
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000921 def GetProp(self, prop):
922 return self.build_props.get(prop)
923
924
Tianjie Xucfa86222016-03-07 16:31:19 -0800925def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
926 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700927 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700928 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700929 self.mount_point = mount_point
930 self.fs_type = fs_type
931 self.device = device
932 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700933 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700934 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700935
936 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800937 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700938 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700939 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700940 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700941
Tao Baod1de6f32017-03-01 16:38:48 -0800942 assert fstab_version == 2
943
944 d = {}
945 for line in data.split("\n"):
946 line = line.strip()
947 if not line or line.startswith("#"):
948 continue
949
950 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
951 pieces = line.split()
952 if len(pieces) != 5:
953 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
954
955 # Ignore entries that are managed by vold.
956 options = pieces[4]
957 if "voldmanaged=" in options:
958 continue
959
960 # It's a good line, parse it.
961 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700962 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800963 options = options.split(",")
964 for i in options:
965 if i.startswith("length="):
966 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700967 elif i == "slotselect":
968 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800969 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800970 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700971 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800972
Tao Baod1de6f32017-03-01 16:38:48 -0800973 mount_flags = pieces[3]
974 # Honor the SELinux context if present.
975 context = None
976 for i in mount_flags.split(","):
977 if i.startswith("context="):
978 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800979
Tao Baod1de6f32017-03-01 16:38:48 -0800980 mount_point = pieces[1]
981 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700982 device=pieces[0], length=length, context=context,
983 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800984
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700985 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700986 # system. Other areas assume system is always at "/system" so point /system
987 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700988 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800989 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700990 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700991 return d
992
993
Tao Bao765668f2019-10-04 22:03:00 -0700994def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
995 """Finds the path to recovery fstab and loads its contents."""
996 # recovery fstab is only meaningful when installing an update via recovery
997 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -0700998 if info_dict.get('ab_update') == 'true' and \
999 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001000 return None
1001
1002 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1003 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1004 # cases, since it may load the info_dict from an old build (e.g. when
1005 # generating incremental OTAs from that build).
1006 system_root_image = info_dict.get('system_root_image') == 'true'
1007 if info_dict.get('no_recovery') != 'true':
1008 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1009 if isinstance(input_file, zipfile.ZipFile):
1010 if recovery_fstab_path not in input_file.namelist():
1011 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1012 else:
1013 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1014 if not os.path.exists(path):
1015 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1016 return LoadRecoveryFSTab(
1017 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1018 system_root_image)
1019
1020 if info_dict.get('recovery_as_boot') == 'true':
1021 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1022 if isinstance(input_file, zipfile.ZipFile):
1023 if recovery_fstab_path not in input_file.namelist():
1024 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1025 else:
1026 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1027 if not os.path.exists(path):
1028 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1029 return LoadRecoveryFSTab(
1030 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1031 system_root_image)
1032
1033 return None
1034
1035
Doug Zongker37974732010-09-16 17:44:38 -07001036def DumpInfoDict(d):
1037 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001038 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001039
Dan Albert8b72aef2015-03-23 19:13:21 -07001040
Daniel Norman55417142019-11-25 16:04:36 -08001041def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001042 """Merges dynamic partition info variables.
1043
1044 Args:
1045 framework_dict: The dictionary of dynamic partition info variables from the
1046 partial framework target files.
1047 vendor_dict: The dictionary of dynamic partition info variables from the
1048 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001049
1050 Returns:
1051 The merged dynamic partition info dictionary.
1052 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001053
1054 def uniq_concat(a, b):
1055 combined = set(a.split(" "))
1056 combined.update(set(b.split(" ")))
1057 combined = [item.strip() for item in combined if item.strip()]
1058 return " ".join(sorted(combined))
1059
1060 if (framework_dict.get("use_dynamic_partitions") !=
1061 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
1062 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1063
1064 merged_dict = {"use_dynamic_partitions": "true"}
1065
1066 merged_dict["dynamic_partition_list"] = uniq_concat(
1067 framework_dict.get("dynamic_partition_list", ""),
1068 vendor_dict.get("dynamic_partition_list", ""))
1069
1070 # Super block devices are defined by the vendor dict.
1071 if "super_block_devices" in vendor_dict:
1072 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1073 for block_device in merged_dict["super_block_devices"].split(" "):
1074 key = "super_%s_device_size" % block_device
1075 if key not in vendor_dict:
1076 raise ValueError("Vendor dict does not contain required key %s." % key)
1077 merged_dict[key] = vendor_dict[key]
1078
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001079 # Partition groups and group sizes are defined by the vendor dict because
1080 # these values may vary for each board that uses a shared system image.
1081 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001082 for partition_group in merged_dict["super_partition_groups"].split(" "):
1083 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001084 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001085 if key not in vendor_dict:
1086 raise ValueError("Vendor dict does not contain required key %s." % key)
1087 merged_dict[key] = vendor_dict[key]
1088
1089 # Set the partition group's partition list using a concatenation of the
1090 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001091 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001092 merged_dict[key] = uniq_concat(
1093 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301094
Daniel Normanb0c75912020-09-24 14:30:21 -07001095 # Various other flags should be copied from the vendor dict, if defined.
1096 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1097 "super_metadata_device", "super_partition_error_limit",
1098 "super_partition_size"):
1099 if key in vendor_dict.keys():
1100 merged_dict[key] = vendor_dict[key]
1101
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001102 return merged_dict
1103
1104
Daniel Norman21c34f72020-11-11 17:25:50 -08001105def PartitionMapFromTargetFiles(target_files_dir):
1106 """Builds a map from partition -> path within an extracted target files directory."""
1107 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1108 possible_subdirs = {
1109 "system": ["SYSTEM"],
1110 "vendor": ["VENDOR", "SYSTEM/vendor"],
1111 "product": ["PRODUCT", "SYSTEM/product"],
1112 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1113 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1114 "vendor_dlkm": [
1115 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1116 ],
1117 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1118 }
1119 partition_map = {}
1120 for partition, subdirs in possible_subdirs.items():
1121 for subdir in subdirs:
1122 if os.path.exists(os.path.join(target_files_dir, subdir)):
1123 partition_map[partition] = subdir
1124 break
1125 return partition_map
1126
1127
Daniel Normand3351562020-10-29 12:33:11 -07001128def SharedUidPartitionViolations(uid_dict, partition_groups):
1129 """Checks for APK sharedUserIds that cross partition group boundaries.
1130
1131 This uses a single or merged build's shareduid_violation_modules.json
1132 output file, as generated by find_shareduid_violation.py or
1133 core/tasks/find-shareduid-violation.mk.
1134
1135 An error is defined as a sharedUserId that is found in a set of partitions
1136 that span more than one partition group.
1137
1138 Args:
1139 uid_dict: A dictionary created by using the standard json module to read a
1140 complete shareduid_violation_modules.json file.
1141 partition_groups: A list of groups, where each group is a list of
1142 partitions.
1143
1144 Returns:
1145 A list of error messages.
1146 """
1147 errors = []
1148 for uid, partitions in uid_dict.items():
1149 found_in_groups = [
1150 group for group in partition_groups
1151 if set(partitions.keys()) & set(group)
1152 ]
1153 if len(found_in_groups) > 1:
1154 errors.append(
1155 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1156 % (uid, ",".join(sorted(partitions.keys()))))
1157 return errors
1158
1159
Daniel Norman21c34f72020-11-11 17:25:50 -08001160def RunHostInitVerifier(product_out, partition_map):
1161 """Runs host_init_verifier on the init rc files within partitions.
1162
1163 host_init_verifier searches the etc/init path within each partition.
1164
1165 Args:
1166 product_out: PRODUCT_OUT directory, containing partition directories.
1167 partition_map: A map of partition name -> relative path within product_out.
1168 """
1169 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1170 cmd = ["host_init_verifier"]
1171 for partition, path in partition_map.items():
1172 if partition not in allowed_partitions:
1173 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1174 partition)
1175 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1176 # Add --property-contexts if the file exists on the partition.
1177 property_contexts = "%s_property_contexts" % (
1178 "plat" if partition == "system" else partition)
1179 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1180 property_contexts)
1181 if os.path.exists(property_contexts_path):
1182 cmd.append("--property-contexts=%s" % property_contexts_path)
1183 # Add the passwd file if the file exists on the partition.
1184 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1185 if os.path.exists(passwd_path):
1186 cmd.extend(["-p", passwd_path])
1187 return RunAndCheckOutput(cmd)
1188
1189
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001190def AppendAVBSigningArgs(cmd, partition):
1191 """Append signing arguments for avbtool."""
1192 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1193 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001194 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1195 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1196 if os.path.exists(new_key_path):
1197 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001198 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1199 if key_path and algorithm:
1200 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001201 avb_salt = OPTIONS.info_dict.get("avb_salt")
1202 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001203 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001204 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001205
1206
Tao Bao765668f2019-10-04 22:03:00 -07001207def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001208 """Returns the VBMeta arguments for partition.
1209
1210 It sets up the VBMeta argument by including the partition descriptor from the
1211 given 'image', or by configuring the partition as a chained partition.
1212
1213 Args:
1214 partition: The name of the partition (e.g. "system").
1215 image: The path to the partition image.
1216 info_dict: A dict returned by common.LoadInfoDict(). Will use
1217 OPTIONS.info_dict if None has been given.
1218
1219 Returns:
1220 A list of VBMeta arguments.
1221 """
1222 if info_dict is None:
1223 info_dict = OPTIONS.info_dict
1224
1225 # Check if chain partition is used.
1226 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001227 if not key_path:
1228 return ["--include_descriptors_from_image", image]
1229
1230 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1231 # into vbmeta.img. The recovery image will be configured on an independent
1232 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1233 # See details at
1234 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001235 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001236 return []
1237
1238 # Otherwise chain the partition into vbmeta.
1239 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1240 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001241
1242
Tao Bao02a08592018-07-22 12:40:45 -07001243def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1244 """Constructs and returns the arg to build or verify a chained partition.
1245
1246 Args:
1247 partition: The partition name.
1248 info_dict: The info dict to look up the key info and rollback index
1249 location.
1250 key: The key to be used for building or verifying the partition. Defaults to
1251 the key listed in info_dict.
1252
1253 Returns:
1254 A string of form "partition:rollback_index_location:key" that can be used to
1255 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001256 """
1257 if key is None:
1258 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001259 if key and not os.path.exists(key) and OPTIONS.search_path:
1260 new_key_path = os.path.join(OPTIONS.search_path, key)
1261 if os.path.exists(new_key_path):
1262 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001263 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001264 rollback_index_location = info_dict[
1265 "avb_" + partition + "_rollback_index_location"]
1266 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1267
1268
Tianjie20dd8f22020-04-19 15:51:16 -07001269def ConstructAftlMakeImageCommands(output_image):
1270 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001271
1272 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001273 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001274 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1275 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1276 'No AFTL manufacturer key provided.'
1277
1278 vbmeta_image = MakeTempFile()
1279 os.rename(output_image, vbmeta_image)
1280 build_info = BuildInfo(OPTIONS.info_dict)
1281 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001282 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001283 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001284 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001285 "--vbmeta_image_path", vbmeta_image,
1286 "--output", output_image,
1287 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001288 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001289 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1290 "--algorithm", "SHA256_RSA4096",
1291 "--padding", "4096"]
1292 if OPTIONS.aftl_signer_helper:
1293 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001294 return aftl_cmd
1295
1296
1297def AddAftlInclusionProof(output_image):
1298 """Appends the aftl inclusion proof to the vbmeta image."""
1299
1300 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001301 RunAndCheckOutput(aftl_cmd)
1302
1303 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1304 output_image, '--transparency_log_pub_keys',
1305 OPTIONS.aftl_key_path]
1306 RunAndCheckOutput(verify_cmd)
1307
1308
Daniel Norman276f0622019-07-26 14:13:51 -07001309def BuildVBMeta(image_path, partitions, name, needed_partitions):
1310 """Creates a VBMeta image.
1311
1312 It generates the requested VBMeta image. The requested image could be for
1313 top-level or chained VBMeta image, which is determined based on the name.
1314
1315 Args:
1316 image_path: The output path for the new VBMeta image.
1317 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001318 values. Only valid partition names are accepted, as partitions listed
1319 in common.AVB_PARTITIONS and custom partitions listed in
1320 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001321 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1322 needed_partitions: Partitions whose descriptors should be included into the
1323 generated VBMeta image.
1324
1325 Raises:
1326 AssertionError: On invalid input args.
1327 """
1328 avbtool = OPTIONS.info_dict["avb_avbtool"]
1329 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1330 AppendAVBSigningArgs(cmd, name)
1331
Hongguang Chenf23364d2020-04-27 18:36:36 -07001332 custom_partitions = OPTIONS.info_dict.get(
1333 "avb_custom_images_partition_list", "").strip().split()
1334
Daniel Norman276f0622019-07-26 14:13:51 -07001335 for partition, path in partitions.items():
1336 if partition not in needed_partitions:
1337 continue
1338 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001339 partition in AVB_VBMETA_PARTITIONS or
1340 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001341 'Unknown partition: {}'.format(partition)
1342 assert os.path.exists(path), \
1343 'Failed to find {} for {}'.format(path, partition)
1344 cmd.extend(GetAvbPartitionArg(partition, path))
1345
1346 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1347 if args and args.strip():
1348 split_args = shlex.split(args)
1349 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001350 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001351 # as a path relative to source tree, which may not be available at the
1352 # same location when running this script (we have the input target_files
1353 # zip only). For such cases, we additionally scan other locations (e.g.
1354 # IMAGES/, RADIO/, etc) before bailing out.
1355 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001356 chained_image = split_args[index + 1]
1357 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001358 continue
1359 found = False
1360 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1361 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001362 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001363 if os.path.exists(alt_path):
1364 split_args[index + 1] = alt_path
1365 found = True
1366 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001367 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001368 cmd.extend(split_args)
1369
1370 RunAndCheckOutput(cmd)
1371
Tianjie Xueaed60c2020-03-12 00:33:28 -07001372 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001373 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001374 AddAftlInclusionProof(image_path)
1375
Daniel Norman276f0622019-07-26 14:13:51 -07001376
J. Avila98cd4cc2020-06-10 20:09:10 +00001377def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001378 ramdisk_img = tempfile.NamedTemporaryFile()
1379
1380 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1381 cmd = ["mkbootfs", "-f", fs_config_file,
1382 os.path.join(sourcedir, "RAMDISK")]
1383 else:
1384 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1385 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001386 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001387 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001388 stdout=ramdisk_img.file.fileno())
1389 else:
1390 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001391
1392 p2.wait()
1393 p1.wait()
1394 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001395 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001396
1397 return ramdisk_img
1398
1399
Steve Muckle9793cf62020-04-08 18:27:00 -07001400def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001401 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001402 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001403
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001404 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001405 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1406 we are building a two-step special image (i.e. building a recovery image to
1407 be loaded into /boot in two-step OTAs).
1408
1409 Return the image data, or None if sourcedir does not appear to contains files
1410 for building the requested image.
1411 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001412
Yifan Hong63c5ca12020-10-08 11:54:02 -07001413 if info_dict is None:
1414 info_dict = OPTIONS.info_dict
1415
Steve Muckle9793cf62020-04-08 18:27:00 -07001416 # "boot" or "recovery", without extension.
1417 partition_name = os.path.basename(sourcedir).lower()
1418
Yifan Hong63c5ca12020-10-08 11:54:02 -07001419 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001420 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001421 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1422 logger.info("Excluded kernel binary from recovery image.")
1423 else:
1424 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001425 else:
1426 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001427 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001428 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001429 return None
1430
1431 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001432 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001433
Doug Zongkereef39442009-04-02 12:14:19 -07001434 img = tempfile.NamedTemporaryFile()
1435
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001436 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001437 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1438 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001439
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001440 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1441 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1442
Yifan Hong63c5ca12020-10-08 11:54:02 -07001443 cmd = [mkbootimg]
1444 if kernel:
1445 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001446
Benoit Fradina45a8682014-07-14 21:00:43 +02001447 fn = os.path.join(sourcedir, "second")
1448 if os.access(fn, os.F_OK):
1449 cmd.append("--second")
1450 cmd.append(fn)
1451
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001452 fn = os.path.join(sourcedir, "dtb")
1453 if os.access(fn, os.F_OK):
1454 cmd.append("--dtb")
1455 cmd.append(fn)
1456
Doug Zongker171f1cd2009-06-15 22:36:37 -07001457 fn = os.path.join(sourcedir, "cmdline")
1458 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001459 cmd.append("--cmdline")
1460 cmd.append(open(fn).read().rstrip("\n"))
1461
1462 fn = os.path.join(sourcedir, "base")
1463 if os.access(fn, os.F_OK):
1464 cmd.append("--base")
1465 cmd.append(open(fn).read().rstrip("\n"))
1466
Ying Wang4de6b5b2010-08-25 14:29:34 -07001467 fn = os.path.join(sourcedir, "pagesize")
1468 if os.access(fn, os.F_OK):
1469 cmd.append("--pagesize")
1470 cmd.append(open(fn).read().rstrip("\n"))
1471
Steve Mucklef84668e2020-03-16 19:13:46 -07001472 if partition_name == "recovery":
1473 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301474 if not args:
1475 # Fall back to "mkbootimg_args" for recovery image
1476 # in case "recovery_mkbootimg_args" is not set.
1477 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001478 else:
1479 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001480 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001481 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001482
Tao Bao76def242017-11-21 09:25:31 -08001483 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001484 if args and args.strip():
1485 cmd.extend(shlex.split(args))
1486
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001487 if has_ramdisk:
1488 cmd.extend(["--ramdisk", ramdisk_img.name])
1489
Tao Baod95e9fd2015-03-29 23:07:41 -07001490 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001491 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001492 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001493 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001494 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001495 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001496
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001497 if partition_name == "recovery":
1498 if info_dict.get("include_recovery_dtbo") == "true":
1499 fn = os.path.join(sourcedir, "recovery_dtbo")
1500 cmd.extend(["--recovery_dtbo", fn])
1501 if info_dict.get("include_recovery_acpio") == "true":
1502 fn = os.path.join(sourcedir, "recovery_acpio")
1503 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001504
Tao Bao986ee862018-10-04 15:46:16 -07001505 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001506
Tao Bao76def242017-11-21 09:25:31 -08001507 if (info_dict.get("boot_signer") == "true" and
1508 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001509 # Hard-code the path as "/boot" for two-step special recovery image (which
1510 # will be loaded into /boot during the two-step OTA).
1511 if two_step_image:
1512 path = "/boot"
1513 else:
Tao Baobf70c312017-07-11 17:27:55 -07001514 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001515 cmd = [OPTIONS.boot_signer_path]
1516 cmd.extend(OPTIONS.boot_signer_args)
1517 cmd.extend([path, img.name,
1518 info_dict["verity_key"] + ".pk8",
1519 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001520 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001521
Tao Baod95e9fd2015-03-29 23:07:41 -07001522 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001523 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001524 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001525 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001526 # We have switched from the prebuilt futility binary to using the tool
1527 # (futility-host) built from the source. Override the setting in the old
1528 # TF.zip.
1529 futility = info_dict["futility"]
1530 if futility.startswith("prebuilts/"):
1531 futility = "futility-host"
1532 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001533 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001534 info_dict["vboot_key"] + ".vbprivk",
1535 info_dict["vboot_subkey"] + ".vbprivk",
1536 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001537 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001538 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001539
Tao Baof3282b42015-04-01 11:21:55 -07001540 # Clean up the temp files.
1541 img_unsigned.close()
1542 img_keyblock.close()
1543
David Zeuthen8fecb282017-12-01 16:24:01 -05001544 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001545 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001546 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001547 if partition_name == "recovery":
1548 part_size = info_dict["recovery_size"]
1549 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001550 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001551 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001552 "--partition_size", str(part_size), "--partition_name",
1553 partition_name]
1554 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001555 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001556 if args and args.strip():
1557 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001558 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001559
1560 img.seek(os.SEEK_SET, 0)
1561 data = img.read()
1562
1563 if has_ramdisk:
1564 ramdisk_img.close()
1565 img.close()
1566
1567 return data
1568
1569
Doug Zongkerd5131602012-08-02 14:46:42 -07001570def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001571 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001572 """Return a File object with the desired bootable image.
1573
1574 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1575 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1576 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001577
Doug Zongker55d93282011-01-25 17:03:34 -08001578 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1579 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001580 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001581 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001582
1583 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1584 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001585 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001586 return File.FromLocalFile(name, prebuilt_path)
1587
Tao Bao32fcdab2018-10-12 10:30:39 -07001588 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001589
1590 if info_dict is None:
1591 info_dict = OPTIONS.info_dict
1592
1593 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001594 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1595 # for recovery.
1596 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1597 prebuilt_name != "boot.img" or
1598 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001599
Doug Zongker6f1d0312014-08-22 08:07:12 -07001600 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001601 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001602 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001603 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001604 if data:
1605 return File(name, data)
1606 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001607
Doug Zongkereef39442009-04-02 12:14:19 -07001608
Steve Mucklee1b10862019-07-10 10:49:37 -07001609def _BuildVendorBootImage(sourcedir, info_dict=None):
1610 """Build a vendor boot image from the specified sourcedir.
1611
1612 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1613 turn them into a vendor boot image.
1614
1615 Return the image data, or None if sourcedir does not appear to contains files
1616 for building the requested image.
1617 """
1618
1619 if info_dict is None:
1620 info_dict = OPTIONS.info_dict
1621
1622 img = tempfile.NamedTemporaryFile()
1623
J. Avila98cd4cc2020-06-10 20:09:10 +00001624 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1625 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001626
1627 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1628 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1629
1630 cmd = [mkbootimg]
1631
1632 fn = os.path.join(sourcedir, "dtb")
1633 if os.access(fn, os.F_OK):
1634 cmd.append("--dtb")
1635 cmd.append(fn)
1636
1637 fn = os.path.join(sourcedir, "vendor_cmdline")
1638 if os.access(fn, os.F_OK):
1639 cmd.append("--vendor_cmdline")
1640 cmd.append(open(fn).read().rstrip("\n"))
1641
1642 fn = os.path.join(sourcedir, "base")
1643 if os.access(fn, os.F_OK):
1644 cmd.append("--base")
1645 cmd.append(open(fn).read().rstrip("\n"))
1646
1647 fn = os.path.join(sourcedir, "pagesize")
1648 if os.access(fn, os.F_OK):
1649 cmd.append("--pagesize")
1650 cmd.append(open(fn).read().rstrip("\n"))
1651
1652 args = info_dict.get("mkbootimg_args")
1653 if args and args.strip():
1654 cmd.extend(shlex.split(args))
1655
1656 args = info_dict.get("mkbootimg_version_args")
1657 if args and args.strip():
1658 cmd.extend(shlex.split(args))
1659
1660 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1661 cmd.extend(["--vendor_boot", img.name])
1662
1663 RunAndCheckOutput(cmd)
1664
1665 # AVB: if enabled, calculate and add hash.
1666 if info_dict.get("avb_enable") == "true":
1667 avbtool = info_dict["avb_avbtool"]
1668 part_size = info_dict["vendor_boot_size"]
1669 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001670 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001671 AppendAVBSigningArgs(cmd, "vendor_boot")
1672 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1673 if args and args.strip():
1674 cmd.extend(shlex.split(args))
1675 RunAndCheckOutput(cmd)
1676
1677 img.seek(os.SEEK_SET, 0)
1678 data = img.read()
1679
1680 ramdisk_img.close()
1681 img.close()
1682
1683 return data
1684
1685
1686def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1687 info_dict=None):
1688 """Return a File object with the desired vendor boot image.
1689
1690 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1691 the source files in 'unpack_dir'/'tree_subdir'."""
1692
1693 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1694 if os.path.exists(prebuilt_path):
1695 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1696 return File.FromLocalFile(name, prebuilt_path)
1697
1698 logger.info("building image from target_files %s...", tree_subdir)
1699
1700 if info_dict is None:
1701 info_dict = OPTIONS.info_dict
1702
Kelvin Zhang0876c412020-06-23 15:06:58 -04001703 data = _BuildVendorBootImage(
1704 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001705 if data:
1706 return File(name, data)
1707 return None
1708
1709
Narayan Kamatha07bf042017-08-14 14:49:21 +01001710def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001711 """Gunzips the given gzip compressed file to a given output file."""
1712 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001713 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001714 shutil.copyfileobj(in_file, out_file)
1715
1716
Tao Bao0ff15de2019-03-20 11:26:06 -07001717def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001718 """Unzips the archive to the given directory.
1719
1720 Args:
1721 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001722 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001723 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1724 archvie. Non-matching patterns will be filtered out. If there's no match
1725 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001726 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001727 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001728 if patterns is not None:
1729 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001730 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001731 names = input_zip.namelist()
1732 filtered = [
1733 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1734
1735 # There isn't any matching files. Don't unzip anything.
1736 if not filtered:
1737 return
1738 cmd.extend(filtered)
1739
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001740 RunAndCheckOutput(cmd)
1741
1742
Doug Zongker75f17362009-12-08 13:46:44 -08001743def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001744 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001745
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001746 Args:
1747 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1748 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1749
1750 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1751 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001752
Tao Bao1c830bf2017-12-25 10:43:47 -08001753 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001754 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001755 """
Doug Zongkereef39442009-04-02 12:14:19 -07001756
Tao Bao1c830bf2017-12-25 10:43:47 -08001757 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001758 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1759 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001760 UnzipToDir(m.group(1), tmp, pattern)
1761 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001762 filename = m.group(1)
1763 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001764 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001765
Tao Baodba59ee2018-01-09 13:21:02 -08001766 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001767
1768
Yifan Hong8a66a712019-04-04 15:37:57 -07001769def GetUserImage(which, tmpdir, input_zip,
1770 info_dict=None,
1771 allow_shared_blocks=None,
1772 hashtree_info_generator=None,
1773 reset_file_map=False):
1774 """Returns an Image object suitable for passing to BlockImageDiff.
1775
1776 This function loads the specified image from the given path. If the specified
1777 image is sparse, it also performs additional processing for OTA purpose. For
1778 example, it always adds block 0 to clobbered blocks list. It also detects
1779 files that cannot be reconstructed from the block list, for whom we should
1780 avoid applying imgdiff.
1781
1782 Args:
1783 which: The partition name.
1784 tmpdir: The directory that contains the prebuilt image and block map file.
1785 input_zip: The target-files ZIP archive.
1786 info_dict: The dict to be looked up for relevant info.
1787 allow_shared_blocks: If image is sparse, whether having shared blocks is
1788 allowed. If none, it is looked up from info_dict.
1789 hashtree_info_generator: If present and image is sparse, generates the
1790 hashtree_info for this sparse image.
1791 reset_file_map: If true and image is sparse, reset file map before returning
1792 the image.
1793 Returns:
1794 A Image object. If it is a sparse image and reset_file_map is False, the
1795 image will have file_map info loaded.
1796 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001797 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001798 info_dict = LoadInfoDict(input_zip)
1799
1800 is_sparse = info_dict.get("extfs_sparse_flag")
1801
1802 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1803 # shared blocks (i.e. some blocks will show up in multiple files' block
1804 # list). We can only allocate such shared blocks to the first "owner", and
1805 # disable imgdiff for all later occurrences.
1806 if allow_shared_blocks is None:
1807 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1808
1809 if is_sparse:
1810 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1811 hashtree_info_generator)
1812 if reset_file_map:
1813 img.ResetFileMap()
1814 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001815 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001816
1817
1818def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1819 """Returns a Image object suitable for passing to BlockImageDiff.
1820
1821 This function loads the specified non-sparse image from the given path.
1822
1823 Args:
1824 which: The partition name.
1825 tmpdir: The directory that contains the prebuilt image and block map file.
1826 Returns:
1827 A Image object.
1828 """
1829 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1830 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1831
1832 # The image and map files must have been created prior to calling
1833 # ota_from_target_files.py (since LMP).
1834 assert os.path.exists(path) and os.path.exists(mappath)
1835
Tianjie Xu41976c72019-07-03 13:57:01 -07001836 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1837
Yifan Hong8a66a712019-04-04 15:37:57 -07001838
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001839def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1840 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001841 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1842
1843 This function loads the specified sparse image from the given path, and
1844 performs additional processing for OTA purpose. For example, it always adds
1845 block 0 to clobbered blocks list. It also detects files that cannot be
1846 reconstructed from the block list, for whom we should avoid applying imgdiff.
1847
1848 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001849 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001850 tmpdir: The directory that contains the prebuilt image and block map file.
1851 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001852 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001853 hashtree_info_generator: If present, generates the hashtree_info for this
1854 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001855 Returns:
1856 A SparseImage object, with file_map info loaded.
1857 """
Tao Baoc765cca2018-01-31 17:32:40 -08001858 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1859 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1860
1861 # The image and map files must have been created prior to calling
1862 # ota_from_target_files.py (since LMP).
1863 assert os.path.exists(path) and os.path.exists(mappath)
1864
1865 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1866 # it to clobbered_blocks so that it will be written to the target
1867 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1868 clobbered_blocks = "0"
1869
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001870 image = sparse_img.SparseImage(
1871 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1872 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001873
1874 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1875 # if they contain all zeros. We can't reconstruct such a file from its block
1876 # list. Tag such entries accordingly. (Bug: 65213616)
1877 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001878 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001879 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001880 continue
1881
Tom Cherryd14b8952018-08-09 14:26:00 -07001882 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1883 # filename listed in system.map may contain an additional leading slash
1884 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1885 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001886 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001887
Tom Cherryd14b8952018-08-09 14:26:00 -07001888 # Special handling another case, where files not under /system
1889 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001890 if which == 'system' and not arcname.startswith('SYSTEM'):
1891 arcname = 'ROOT/' + arcname
1892
1893 assert arcname in input_zip.namelist(), \
1894 "Failed to find the ZIP entry for {}".format(entry)
1895
Tao Baoc765cca2018-01-31 17:32:40 -08001896 info = input_zip.getinfo(arcname)
1897 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001898
1899 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001900 # image, check the original block list to determine its completeness. Note
1901 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001902 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001903 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001904
Tao Baoc765cca2018-01-31 17:32:40 -08001905 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1906 ranges.extra['incomplete'] = True
1907
1908 return image
1909
1910
Doug Zongkereef39442009-04-02 12:14:19 -07001911def GetKeyPasswords(keylist):
1912 """Given a list of keys, prompt the user to enter passwords for
1913 those which require them. Return a {key: password} dict. password
1914 will be None if the key has no password."""
1915
Doug Zongker8ce7c252009-05-22 13:34:54 -07001916 no_passwords = []
1917 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001918 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001919 devnull = open("/dev/null", "w+b")
1920 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001921 # We don't need a password for things that aren't really keys.
1922 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001923 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001924 continue
1925
T.R. Fullhart37e10522013-03-18 10:31:26 -07001926 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001927 "-inform", "DER", "-nocrypt"],
1928 stdin=devnull.fileno(),
1929 stdout=devnull.fileno(),
1930 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001931 p.communicate()
1932 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001933 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001934 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001935 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001936 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1937 "-inform", "DER", "-passin", "pass:"],
1938 stdin=devnull.fileno(),
1939 stdout=devnull.fileno(),
1940 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001941 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001942 if p.returncode == 0:
1943 # Encrypted key with empty string as password.
1944 key_passwords[k] = ''
1945 elif stderr.startswith('Error decrypting key'):
1946 # Definitely encrypted key.
1947 # It would have said "Error reading key" if it didn't parse correctly.
1948 need_passwords.append(k)
1949 else:
1950 # Potentially, a type of key that openssl doesn't understand.
1951 # We'll let the routines in signapk.jar handle it.
1952 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001953 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001954
T.R. Fullhart37e10522013-03-18 10:31:26 -07001955 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001956 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001957 return key_passwords
1958
1959
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001960def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001961 """Gets the minSdkVersion declared in the APK.
1962
changho.shin0f125362019-07-08 10:59:00 +09001963 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001964 This can be both a decimal number (API Level) or a codename.
1965
1966 Args:
1967 apk_name: The APK filename.
1968
1969 Returns:
1970 The parsed SDK version string.
1971
1972 Raises:
1973 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001974 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001975 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001976 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001977 stderr=subprocess.PIPE)
1978 stdoutdata, stderrdata = proc.communicate()
1979 if proc.returncode != 0:
1980 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001981 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001982 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001983
Tao Baof47bf0f2018-03-21 23:28:51 -07001984 for line in stdoutdata.split("\n"):
1985 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001986 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1987 if m:
1988 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001989 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001990
1991
1992def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001993 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001994
Tao Baof47bf0f2018-03-21 23:28:51 -07001995 If minSdkVersion is set to a codename, it is translated to a number using the
1996 provided map.
1997
1998 Args:
1999 apk_name: The APK filename.
2000
2001 Returns:
2002 The parsed SDK version number.
2003
2004 Raises:
2005 ExternalError: On failing to get the min SDK version number.
2006 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002007 version = GetMinSdkVersion(apk_name)
2008 try:
2009 return int(version)
2010 except ValueError:
2011 # Not a decimal number. Codename?
2012 if version in codename_to_api_level_map:
2013 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002014 raise ExternalError(
2015 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2016 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002017
2018
2019def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002020 codename_to_api_level_map=None, whole_file=False,
2021 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002022 """Sign the input_name zip/jar/apk, producing output_name. Use the
2023 given key and password (the latter may be None if the key does not
2024 have a password.
2025
Doug Zongker951495f2009-08-14 12:44:19 -07002026 If whole_file is true, use the "-w" option to SignApk to embed a
2027 signature that covers the whole file in the archive comment of the
2028 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002029
2030 min_api_level is the API Level (int) of the oldest platform this file may end
2031 up on. If not specified for an APK, the API Level is obtained by interpreting
2032 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2033
2034 codename_to_api_level_map is needed to translate the codename which may be
2035 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002036
2037 Caller may optionally specify extra args to be passed to SignApk, which
2038 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002039 """
Tao Bao76def242017-11-21 09:25:31 -08002040 if codename_to_api_level_map is None:
2041 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002042 if extra_signapk_args is None:
2043 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002044
Alex Klyubin9667b182015-12-10 13:38:50 -08002045 java_library_path = os.path.join(
2046 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2047
Tao Baoe95540e2016-11-08 12:08:53 -08002048 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2049 ["-Djava.library.path=" + java_library_path,
2050 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002051 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002052 if whole_file:
2053 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002054
2055 min_sdk_version = min_api_level
2056 if min_sdk_version is None:
2057 if not whole_file:
2058 min_sdk_version = GetMinSdkVersionInt(
2059 input_name, codename_to_api_level_map)
2060 if min_sdk_version is not None:
2061 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2062
T.R. Fullhart37e10522013-03-18 10:31:26 -07002063 cmd.extend([key + OPTIONS.public_key_suffix,
2064 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002065 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002066
Tao Bao73dd4f42018-10-04 16:25:33 -07002067 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002068 if password is not None:
2069 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002070 stdoutdata, _ = proc.communicate(password)
2071 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002072 raise ExternalError(
2073 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002074 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002075
Doug Zongkereef39442009-04-02 12:14:19 -07002076
Doug Zongker37974732010-09-16 17:44:38 -07002077def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002078 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002079
Tao Bao9dd909e2017-11-14 11:27:32 -08002080 For non-AVB images, raise exception if the data is too big. Print a warning
2081 if the data is nearing the maximum size.
2082
2083 For AVB images, the actual image size should be identical to the limit.
2084
2085 Args:
2086 data: A string that contains all the data for the partition.
2087 target: The partition name. The ".img" suffix is optional.
2088 info_dict: The dict to be looked up for relevant info.
2089 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002090 if target.endswith(".img"):
2091 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002092 mount_point = "/" + target
2093
Ying Wangf8824af2014-06-03 14:07:27 -07002094 fs_type = None
2095 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002096 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002097 if mount_point == "/userdata":
2098 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002099 p = info_dict["fstab"][mount_point]
2100 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002101 device = p.device
2102 if "/" in device:
2103 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002104 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002105 if not fs_type or not limit:
2106 return
Doug Zongkereef39442009-04-02 12:14:19 -07002107
Andrew Boie0f9aec82012-02-14 09:32:52 -08002108 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002109 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2110 # path.
2111 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2112 if size != limit:
2113 raise ExternalError(
2114 "Mismatching image size for %s: expected %d actual %d" % (
2115 target, limit, size))
2116 else:
2117 pct = float(size) * 100.0 / limit
2118 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2119 if pct >= 99.0:
2120 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002121
2122 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002123 logger.warning("\n WARNING: %s\n", msg)
2124 else:
2125 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002126
2127
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002128def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002129 """Parses the APK certs info from a given target-files zip.
2130
2131 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2132 tuple with the following elements: (1) a dictionary that maps packages to
2133 certs (based on the "certificate" and "private_key" attributes in the file;
2134 (2) a string representing the extension of compressed APKs in the target files
2135 (e.g ".gz", ".bro").
2136
2137 Args:
2138 tf_zip: The input target_files ZipFile (already open).
2139
2140 Returns:
2141 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2142 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2143 no compressed APKs.
2144 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002145 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002146 compressed_extension = None
2147
Tao Bao0f990332017-09-08 19:02:54 -07002148 # META/apkcerts.txt contains the info for _all_ the packages known at build
2149 # time. Filter out the ones that are not installed.
2150 installed_files = set()
2151 for name in tf_zip.namelist():
2152 basename = os.path.basename(name)
2153 if basename:
2154 installed_files.add(basename)
2155
Tao Baoda30cfa2017-12-01 16:19:46 -08002156 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002157 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002158 if not line:
2159 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002160 m = re.match(
2161 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002162 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2163 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002164 line)
2165 if not m:
2166 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002167
Tao Bao818ddf52018-01-05 11:17:34 -08002168 matches = m.groupdict()
2169 cert = matches["CERT"]
2170 privkey = matches["PRIVKEY"]
2171 name = matches["NAME"]
2172 this_compressed_extension = matches["COMPRESSED"]
2173
2174 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2175 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2176 if cert in SPECIAL_CERT_STRINGS and not privkey:
2177 certmap[name] = cert
2178 elif (cert.endswith(OPTIONS.public_key_suffix) and
2179 privkey.endswith(OPTIONS.private_key_suffix) and
2180 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2181 certmap[name] = cert[:-public_key_suffix_len]
2182 else:
2183 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2184
2185 if not this_compressed_extension:
2186 continue
2187
2188 # Only count the installed files.
2189 filename = name + '.' + this_compressed_extension
2190 if filename not in installed_files:
2191 continue
2192
2193 # Make sure that all the values in the compression map have the same
2194 # extension. We don't support multiple compression methods in the same
2195 # system image.
2196 if compressed_extension:
2197 if this_compressed_extension != compressed_extension:
2198 raise ValueError(
2199 "Multiple compressed extensions: {} vs {}".format(
2200 compressed_extension, this_compressed_extension))
2201 else:
2202 compressed_extension = this_compressed_extension
2203
2204 return (certmap,
2205 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002206
2207
Doug Zongkereef39442009-04-02 12:14:19 -07002208COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002209Global options
2210
2211 -p (--path) <dir>
2212 Prepend <dir>/bin to the list of places to search for binaries run by this
2213 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002214
Doug Zongker05d3dea2009-06-22 11:32:31 -07002215 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002216 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002217
Tao Bao30df8b42018-04-23 15:32:53 -07002218 -x (--extra) <key=value>
2219 Add a key/value pair to the 'extras' dict, which device-specific extension
2220 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002221
Doug Zongkereef39442009-04-02 12:14:19 -07002222 -v (--verbose)
2223 Show command lines being executed.
2224
2225 -h (--help)
2226 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002227
2228 --logfile <file>
2229 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002230"""
2231
Kelvin Zhang0876c412020-06-23 15:06:58 -04002232
Doug Zongkereef39442009-04-02 12:14:19 -07002233def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002234 print(docstring.rstrip("\n"))
2235 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002236
2237
2238def ParseOptions(argv,
2239 docstring,
2240 extra_opts="", extra_long_opts=(),
2241 extra_option_handler=None):
2242 """Parse the options in argv and return any arguments that aren't
2243 flags. docstring is the calling module's docstring, to be displayed
2244 for errors and -h. extra_opts and extra_long_opts are for flags
2245 defined by the caller, which are processed by passing them to
2246 extra_option_handler."""
2247
2248 try:
2249 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002250 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002251 ["help", "verbose", "path=", "signapk_path=",
2252 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002253 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002254 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2255 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002256 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2257 "aftl_key_path=", "aftl_manufacturer_key_path=",
2258 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002259 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002260 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002261 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002262 sys.exit(2)
2263
Doug Zongkereef39442009-04-02 12:14:19 -07002264 for o, a in opts:
2265 if o in ("-h", "--help"):
2266 Usage(docstring)
2267 sys.exit()
2268 elif o in ("-v", "--verbose"):
2269 OPTIONS.verbose = True
2270 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002271 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002272 elif o in ("--signapk_path",):
2273 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002274 elif o in ("--signapk_shared_library_path",):
2275 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002276 elif o in ("--extra_signapk_args",):
2277 OPTIONS.extra_signapk_args = shlex.split(a)
2278 elif o in ("--java_path",):
2279 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002280 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002281 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002282 elif o in ("--android_jar_path",):
2283 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002284 elif o in ("--public_key_suffix",):
2285 OPTIONS.public_key_suffix = a
2286 elif o in ("--private_key_suffix",):
2287 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002288 elif o in ("--boot_signer_path",):
2289 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002290 elif o in ("--boot_signer_args",):
2291 OPTIONS.boot_signer_args = shlex.split(a)
2292 elif o in ("--verity_signer_path",):
2293 OPTIONS.verity_signer_path = a
2294 elif o in ("--verity_signer_args",):
2295 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002296 elif o in ("--aftl_tool_path",):
2297 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002298 elif o in ("--aftl_server",):
2299 OPTIONS.aftl_server = a
2300 elif o in ("--aftl_key_path",):
2301 OPTIONS.aftl_key_path = a
2302 elif o in ("--aftl_manufacturer_key_path",):
2303 OPTIONS.aftl_manufacturer_key_path = a
2304 elif o in ("--aftl_signer_helper",):
2305 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002306 elif o in ("-s", "--device_specific"):
2307 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002308 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002309 key, value = a.split("=", 1)
2310 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002311 elif o in ("--logfile",):
2312 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002313 else:
2314 if extra_option_handler is None or not extra_option_handler(o, a):
2315 assert False, "unknown option \"%s\"" % (o,)
2316
Doug Zongker85448772014-09-09 14:59:20 -07002317 if OPTIONS.search_path:
2318 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2319 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002320
2321 return args
2322
2323
Tao Bao4c851b12016-09-19 13:54:38 -07002324def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002325 """Make a temp file and add it to the list of things to be deleted
2326 when Cleanup() is called. Return the filename."""
2327 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2328 os.close(fd)
2329 OPTIONS.tempfiles.append(fn)
2330 return fn
2331
2332
Tao Bao1c830bf2017-12-25 10:43:47 -08002333def MakeTempDir(prefix='tmp', suffix=''):
2334 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2335
2336 Returns:
2337 The absolute pathname of the new directory.
2338 """
2339 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2340 OPTIONS.tempfiles.append(dir_name)
2341 return dir_name
2342
2343
Doug Zongkereef39442009-04-02 12:14:19 -07002344def Cleanup():
2345 for i in OPTIONS.tempfiles:
2346 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002347 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002348 else:
2349 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002350 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002351
2352
2353class PasswordManager(object):
2354 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002355 self.editor = os.getenv("EDITOR")
2356 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002357
2358 def GetPasswords(self, items):
2359 """Get passwords corresponding to each string in 'items',
2360 returning a dict. (The dict may have keys in addition to the
2361 values in 'items'.)
2362
2363 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2364 user edit that file to add more needed passwords. If no editor is
2365 available, or $ANDROID_PW_FILE isn't define, prompts the user
2366 interactively in the ordinary way.
2367 """
2368
2369 current = self.ReadFile()
2370
2371 first = True
2372 while True:
2373 missing = []
2374 for i in items:
2375 if i not in current or not current[i]:
2376 missing.append(i)
2377 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002378 if not missing:
2379 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002380
2381 for i in missing:
2382 current[i] = ""
2383
2384 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002385 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002386 if sys.version_info[0] >= 3:
2387 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002388 answer = raw_input("try to edit again? [y]> ").strip()
2389 if answer and answer[0] not in 'yY':
2390 raise RuntimeError("key passwords unavailable")
2391 first = False
2392
2393 current = self.UpdateAndReadFile(current)
2394
Kelvin Zhang0876c412020-06-23 15:06:58 -04002395 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002396 """Prompt the user to enter a value (password) for each key in
2397 'current' whose value is fales. Returns a new dict with all the
2398 values.
2399 """
2400 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002401 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002402 if v:
2403 result[k] = v
2404 else:
2405 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002406 result[k] = getpass.getpass(
2407 "Enter password for %s key> " % k).strip()
2408 if result[k]:
2409 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002410 return result
2411
2412 def UpdateAndReadFile(self, current):
2413 if not self.editor or not self.pwfile:
2414 return self.PromptResult(current)
2415
2416 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002417 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002418 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2419 f.write("# (Additional spaces are harmless.)\n\n")
2420
2421 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002422 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002423 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002424 f.write("[[[ %s ]]] %s\n" % (v, k))
2425 if not v and first_line is None:
2426 # position cursor on first line with no password.
2427 first_line = i + 4
2428 f.close()
2429
Tao Bao986ee862018-10-04 15:46:16 -07002430 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002431
2432 return self.ReadFile()
2433
2434 def ReadFile(self):
2435 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002436 if self.pwfile is None:
2437 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002438 try:
2439 f = open(self.pwfile, "r")
2440 for line in f:
2441 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002442 if not line or line[0] == '#':
2443 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002444 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2445 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002446 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002447 else:
2448 result[m.group(2)] = m.group(1)
2449 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002450 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002451 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002452 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002453 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002454
2455
Dan Albert8e0178d2015-01-27 15:53:15 -08002456def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2457 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002458
2459 # http://b/18015246
2460 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2461 # for files larger than 2GiB. We can work around this by adjusting their
2462 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2463 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2464 # it isn't clear to me exactly what circumstances cause this).
2465 # `zipfile.write()` must be used directly to work around this.
2466 #
2467 # This mess can be avoided if we port to python3.
2468 saved_zip64_limit = zipfile.ZIP64_LIMIT
2469 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2470
2471 if compress_type is None:
2472 compress_type = zip_file.compression
2473 if arcname is None:
2474 arcname = filename
2475
2476 saved_stat = os.stat(filename)
2477
2478 try:
2479 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2480 # file to be zipped and reset it when we're done.
2481 os.chmod(filename, perms)
2482
2483 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002484 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2485 # intentional. zip stores datetimes in local time without a time zone
2486 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2487 # in the zip archive.
2488 local_epoch = datetime.datetime.fromtimestamp(0)
2489 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002490 os.utime(filename, (timestamp, timestamp))
2491
2492 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2493 finally:
2494 os.chmod(filename, saved_stat.st_mode)
2495 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2496 zipfile.ZIP64_LIMIT = saved_zip64_limit
2497
2498
Tao Bao58c1b962015-05-20 09:32:18 -07002499def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002500 compress_type=None):
2501 """Wrap zipfile.writestr() function to work around the zip64 limit.
2502
2503 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2504 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2505 when calling crc32(bytes).
2506
2507 But it still works fine to write a shorter string into a large zip file.
2508 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2509 when we know the string won't be too long.
2510 """
2511
2512 saved_zip64_limit = zipfile.ZIP64_LIMIT
2513 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2514
2515 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2516 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002517 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002518 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002519 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002520 else:
Tao Baof3282b42015-04-01 11:21:55 -07002521 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002522 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2523 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2524 # such a case (since
2525 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2526 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2527 # permission bits. We follow the logic in Python 3 to get consistent
2528 # behavior between using the two versions.
2529 if not zinfo.external_attr:
2530 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002531
2532 # If compress_type is given, it overrides the value in zinfo.
2533 if compress_type is not None:
2534 zinfo.compress_type = compress_type
2535
Tao Bao58c1b962015-05-20 09:32:18 -07002536 # If perms is given, it has a priority.
2537 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002538 # If perms doesn't set the file type, mark it as a regular file.
2539 if perms & 0o770000 == 0:
2540 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002541 zinfo.external_attr = perms << 16
2542
Tao Baof3282b42015-04-01 11:21:55 -07002543 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002544 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2545
Dan Albert8b72aef2015-03-23 19:13:21 -07002546 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002547 zipfile.ZIP64_LIMIT = saved_zip64_limit
2548
2549
Tao Bao89d7ab22017-12-14 17:05:33 -08002550def ZipDelete(zip_filename, entries):
2551 """Deletes entries from a ZIP file.
2552
2553 Since deleting entries from a ZIP file is not supported, it shells out to
2554 'zip -d'.
2555
2556 Args:
2557 zip_filename: The name of the ZIP file.
2558 entries: The name of the entry, or the list of names to be deleted.
2559
2560 Raises:
2561 AssertionError: In case of non-zero return from 'zip'.
2562 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002563 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002564 entries = [entries]
2565 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002566 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002567
2568
Tao Baof3282b42015-04-01 11:21:55 -07002569def ZipClose(zip_file):
2570 # http://b/18015246
2571 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2572 # central directory.
2573 saved_zip64_limit = zipfile.ZIP64_LIMIT
2574 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2575
2576 zip_file.close()
2577
2578 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002579
2580
2581class DeviceSpecificParams(object):
2582 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002583
Doug Zongker05d3dea2009-06-22 11:32:31 -07002584 def __init__(self, **kwargs):
2585 """Keyword arguments to the constructor become attributes of this
2586 object, which is passed to all functions in the device-specific
2587 module."""
Tao Bao38884282019-07-10 22:20:56 -07002588 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002589 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002590 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002591
2592 if self.module is None:
2593 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002594 if not path:
2595 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002596 try:
2597 if os.path.isdir(path):
2598 info = imp.find_module("releasetools", [path])
2599 else:
2600 d, f = os.path.split(path)
2601 b, x = os.path.splitext(f)
2602 if x == ".py":
2603 f = b
2604 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002605 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002606 self.module = imp.load_module("device_specific", *info)
2607 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002608 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002609
2610 def _DoCall(self, function_name, *args, **kwargs):
2611 """Call the named function in the device-specific module, passing
2612 the given args and kwargs. The first argument to the call will be
2613 the DeviceSpecific object itself. If there is no module, or the
2614 module does not define the function, return the value of the
2615 'default' kwarg (which itself defaults to None)."""
2616 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002617 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002618 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2619
2620 def FullOTA_Assertions(self):
2621 """Called after emitting the block of assertions at the top of a
2622 full OTA package. Implementations can add whatever additional
2623 assertions they like."""
2624 return self._DoCall("FullOTA_Assertions")
2625
Doug Zongkere5ff5902012-01-17 10:55:37 -08002626 def FullOTA_InstallBegin(self):
2627 """Called at the start of full OTA installation."""
2628 return self._DoCall("FullOTA_InstallBegin")
2629
Yifan Hong10c530d2018-12-27 17:34:18 -08002630 def FullOTA_GetBlockDifferences(self):
2631 """Called during full OTA installation and verification.
2632 Implementation should return a list of BlockDifference objects describing
2633 the update on each additional partitions.
2634 """
2635 return self._DoCall("FullOTA_GetBlockDifferences")
2636
Doug Zongker05d3dea2009-06-22 11:32:31 -07002637 def FullOTA_InstallEnd(self):
2638 """Called at the end of full OTA installation; typically this is
2639 used to install the image for the device's baseband processor."""
2640 return self._DoCall("FullOTA_InstallEnd")
2641
2642 def IncrementalOTA_Assertions(self):
2643 """Called after emitting the block of assertions at the top of an
2644 incremental OTA package. Implementations can add whatever
2645 additional assertions they like."""
2646 return self._DoCall("IncrementalOTA_Assertions")
2647
Doug Zongkere5ff5902012-01-17 10:55:37 -08002648 def IncrementalOTA_VerifyBegin(self):
2649 """Called at the start of the verification phase of incremental
2650 OTA installation; additional checks can be placed here to abort
2651 the script before any changes are made."""
2652 return self._DoCall("IncrementalOTA_VerifyBegin")
2653
Doug Zongker05d3dea2009-06-22 11:32:31 -07002654 def IncrementalOTA_VerifyEnd(self):
2655 """Called at the end of the verification phase of incremental OTA
2656 installation; additional checks can be placed here to abort the
2657 script before any changes are made."""
2658 return self._DoCall("IncrementalOTA_VerifyEnd")
2659
Doug Zongkere5ff5902012-01-17 10:55:37 -08002660 def IncrementalOTA_InstallBegin(self):
2661 """Called at the start of incremental OTA installation (after
2662 verification is complete)."""
2663 return self._DoCall("IncrementalOTA_InstallBegin")
2664
Yifan Hong10c530d2018-12-27 17:34:18 -08002665 def IncrementalOTA_GetBlockDifferences(self):
2666 """Called during incremental OTA installation and verification.
2667 Implementation should return a list of BlockDifference objects describing
2668 the update on each additional partitions.
2669 """
2670 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2671
Doug Zongker05d3dea2009-06-22 11:32:31 -07002672 def IncrementalOTA_InstallEnd(self):
2673 """Called at the end of incremental OTA installation; typically
2674 this is used to install the image for the device's baseband
2675 processor."""
2676 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002677
Tao Bao9bc6bb22015-11-09 16:58:28 -08002678 def VerifyOTA_Assertions(self):
2679 return self._DoCall("VerifyOTA_Assertions")
2680
Tao Bao76def242017-11-21 09:25:31 -08002681
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002682class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002683 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002684 self.name = name
2685 self.data = data
2686 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002687 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002688 self.sha1 = sha1(data).hexdigest()
2689
2690 @classmethod
2691 def FromLocalFile(cls, name, diskname):
2692 f = open(diskname, "rb")
2693 data = f.read()
2694 f.close()
2695 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002696
2697 def WriteToTemp(self):
2698 t = tempfile.NamedTemporaryFile()
2699 t.write(self.data)
2700 t.flush()
2701 return t
2702
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002703 def WriteToDir(self, d):
2704 with open(os.path.join(d, self.name), "wb") as fp:
2705 fp.write(self.data)
2706
Geremy Condra36bd3652014-02-06 19:45:10 -08002707 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002708 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002709
Tao Bao76def242017-11-21 09:25:31 -08002710
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002711DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002712 ".gz": "imgdiff",
2713 ".zip": ["imgdiff", "-z"],
2714 ".jar": ["imgdiff", "-z"],
2715 ".apk": ["imgdiff", "-z"],
2716 ".img": "imgdiff",
2717}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002718
Tao Bao76def242017-11-21 09:25:31 -08002719
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002720class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002721 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002722 self.tf = tf
2723 self.sf = sf
2724 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002725 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002726
2727 def ComputePatch(self):
2728 """Compute the patch (as a string of data) needed to turn sf into
2729 tf. Returns the same tuple as GetPatch()."""
2730
2731 tf = self.tf
2732 sf = self.sf
2733
Doug Zongker24cd2802012-08-14 16:36:15 -07002734 if self.diff_program:
2735 diff_program = self.diff_program
2736 else:
2737 ext = os.path.splitext(tf.name)[1]
2738 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002739
2740 ttemp = tf.WriteToTemp()
2741 stemp = sf.WriteToTemp()
2742
2743 ext = os.path.splitext(tf.name)[1]
2744
2745 try:
2746 ptemp = tempfile.NamedTemporaryFile()
2747 if isinstance(diff_program, list):
2748 cmd = copy.copy(diff_program)
2749 else:
2750 cmd = [diff_program]
2751 cmd.append(stemp.name)
2752 cmd.append(ttemp.name)
2753 cmd.append(ptemp.name)
2754 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002755 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002756
Doug Zongkerf8340082014-08-05 10:39:37 -07002757 def run():
2758 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002759 if e:
2760 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002761 th = threading.Thread(target=run)
2762 th.start()
2763 th.join(timeout=300) # 5 mins
2764 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002765 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002766 p.terminate()
2767 th.join(5)
2768 if th.is_alive():
2769 p.kill()
2770 th.join()
2771
Tianjie Xua2a9f992018-01-05 15:15:54 -08002772 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002773 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002774 self.patch = None
2775 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002776 diff = ptemp.read()
2777 finally:
2778 ptemp.close()
2779 stemp.close()
2780 ttemp.close()
2781
2782 self.patch = diff
2783 return self.tf, self.sf, self.patch
2784
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002785 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002786 """Returns a tuple of (target_file, source_file, patch_data).
2787
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002788 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002789 computing the patch failed.
2790 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002791 return self.tf, self.sf, self.patch
2792
2793
2794def ComputeDifferences(diffs):
2795 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002796 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002797
2798 # Do the largest files first, to try and reduce the long-pole effect.
2799 by_size = [(i.tf.size, i) for i in diffs]
2800 by_size.sort(reverse=True)
2801 by_size = [i[1] for i in by_size]
2802
2803 lock = threading.Lock()
2804 diff_iter = iter(by_size) # accessed under lock
2805
2806 def worker():
2807 try:
2808 lock.acquire()
2809 for d in diff_iter:
2810 lock.release()
2811 start = time.time()
2812 d.ComputePatch()
2813 dur = time.time() - start
2814 lock.acquire()
2815
2816 tf, sf, patch = d.GetPatch()
2817 if sf.name == tf.name:
2818 name = tf.name
2819 else:
2820 name = "%s (%s)" % (tf.name, sf.name)
2821 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002822 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002823 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002824 logger.info(
2825 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2826 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002827 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002828 except Exception:
2829 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002830 raise
2831
2832 # start worker threads; wait for them all to finish.
2833 threads = [threading.Thread(target=worker)
2834 for i in range(OPTIONS.worker_threads)]
2835 for th in threads:
2836 th.start()
2837 while threads:
2838 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002839
2840
Dan Albert8b72aef2015-03-23 19:13:21 -07002841class BlockDifference(object):
2842 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002843 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002844 self.tgt = tgt
2845 self.src = src
2846 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002847 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002848 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002849
Tao Baodd2a5892015-03-12 12:32:37 -07002850 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002851 version = max(
2852 int(i) for i in
2853 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002854 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002855 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002856
Tianjie Xu41976c72019-07-03 13:57:01 -07002857 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2858 version=self.version,
2859 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002860 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002861 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002862 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002863 self.touched_src_ranges = b.touched_src_ranges
2864 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002865
Yifan Hong10c530d2018-12-27 17:34:18 -08002866 # On devices with dynamic partitions, for new partitions,
2867 # src is None but OPTIONS.source_info_dict is not.
2868 if OPTIONS.source_info_dict is None:
2869 is_dynamic_build = OPTIONS.info_dict.get(
2870 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002871 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002872 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002873 is_dynamic_build = OPTIONS.source_info_dict.get(
2874 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002875 is_dynamic_source = partition in shlex.split(
2876 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002877
Yifan Hongbb2658d2019-01-25 12:30:58 -08002878 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002879 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2880
Yifan Hongbb2658d2019-01-25 12:30:58 -08002881 # For dynamic partitions builds, check partition list in both source
2882 # and target build because new partitions may be added, and existing
2883 # partitions may be removed.
2884 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2885
Yifan Hong10c530d2018-12-27 17:34:18 -08002886 if is_dynamic:
2887 self.device = 'map_partition("%s")' % partition
2888 else:
2889 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002890 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2891 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002892 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002893 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2894 OPTIONS.source_info_dict)
2895 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002896
Tao Baod8d14be2016-02-04 14:26:02 -08002897 @property
2898 def required_cache(self):
2899 return self._required_cache
2900
Tao Bao76def242017-11-21 09:25:31 -08002901 def WriteScript(self, script, output_zip, progress=None,
2902 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002903 if not self.src:
2904 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002905 script.Print("Patching %s image unconditionally..." % (self.partition,))
2906 else:
2907 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002908
Dan Albert8b72aef2015-03-23 19:13:21 -07002909 if progress:
2910 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002911 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002912
2913 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002914 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002915
Tao Bao9bc6bb22015-11-09 16:58:28 -08002916 def WriteStrictVerifyScript(self, script):
2917 """Verify all the blocks in the care_map, including clobbered blocks.
2918
2919 This differs from the WriteVerifyScript() function: a) it prints different
2920 error messages; b) it doesn't allow half-way updated images to pass the
2921 verification."""
2922
2923 partition = self.partition
2924 script.Print("Verifying %s..." % (partition,))
2925 ranges = self.tgt.care_map
2926 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002927 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002928 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2929 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002930 self.device, ranges_str,
2931 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002932 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002933 script.AppendExtra("")
2934
Tao Baod522bdc2016-04-12 15:53:16 -07002935 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002936 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002937
2938 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002939 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002940 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002941
2942 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002943 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002944 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002945 ranges = self.touched_src_ranges
2946 expected_sha1 = self.touched_src_sha1
2947 else:
2948 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2949 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002950
2951 # No blocks to be checked, skipping.
2952 if not ranges:
2953 return
2954
Tao Bao5ece99d2015-05-12 11:42:31 -07002955 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002956 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002957 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002958 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2959 '"%s.patch.dat")) then' % (
2960 self.device, ranges_str, expected_sha1,
2961 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002962 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002963 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002964
Tianjie Xufc3422a2015-12-15 11:53:59 -08002965 if self.version >= 4:
2966
2967 # Bug: 21124327
2968 # When generating incrementals for the system and vendor partitions in
2969 # version 4 or newer, explicitly check the first block (which contains
2970 # the superblock) of the partition to see if it's what we expect. If
2971 # this check fails, give an explicit log message about the partition
2972 # having been remounted R/W (the most likely explanation).
2973 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002974 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002975
2976 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002977 if partition == "system":
2978 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2979 else:
2980 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002981 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002982 'ifelse (block_image_recover({device}, "{ranges}") && '
2983 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002984 'package_extract_file("{partition}.transfer.list"), '
2985 '"{partition}.new.dat", "{partition}.patch.dat"), '
2986 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002987 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002988 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002989 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002990
Tao Baodd2a5892015-03-12 12:32:37 -07002991 # Abort the OTA update. Note that the incremental OTA cannot be applied
2992 # even if it may match the checksum of the target partition.
2993 # a) If version < 3, operations like move and erase will make changes
2994 # unconditionally and damage the partition.
2995 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002996 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002997 if partition == "system":
2998 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2999 else:
3000 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3001 script.AppendExtra((
3002 'abort("E%d: %s partition has unexpected contents");\n'
3003 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003004
Yifan Hong10c530d2018-12-27 17:34:18 -08003005 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003006 partition = self.partition
3007 script.Print('Verifying the updated %s image...' % (partition,))
3008 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3009 ranges = self.tgt.care_map
3010 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003011 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003012 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003013 self.device, ranges_str,
3014 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003015
3016 # Bug: 20881595
3017 # Verify that extended blocks are really zeroed out.
3018 if self.tgt.extended:
3019 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003020 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003021 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003022 self.device, ranges_str,
3023 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003024 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003025 if partition == "system":
3026 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3027 else:
3028 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003029 script.AppendExtra(
3030 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003031 ' abort("E%d: %s partition has unexpected non-zero contents after '
3032 'OTA update");\n'
3033 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003034 else:
3035 script.Print('Verified the updated %s image.' % (partition,))
3036
Tianjie Xu209db462016-05-24 17:34:52 -07003037 if partition == "system":
3038 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3039 else:
3040 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3041
Tao Bao5fcaaef2015-06-01 13:40:49 -07003042 script.AppendExtra(
3043 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003044 ' abort("E%d: %s partition has unexpected contents after OTA '
3045 'update");\n'
3046 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003047
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003048 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003049 ZipWrite(output_zip,
3050 '{}.transfer.list'.format(self.path),
3051 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003052
Tao Bao76def242017-11-21 09:25:31 -08003053 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3054 # its size. Quailty 9 almost triples the compression time but doesn't
3055 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003056 # zip | brotli(quality 6) | brotli(quality 9)
3057 # compressed_size: 942M | 869M (~8% reduced) | 854M
3058 # compression_time: 75s | 265s | 719s
3059 # decompression_time: 15s | 25s | 25s
3060
3061 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003062 brotli_cmd = ['brotli', '--quality=6',
3063 '--output={}.new.dat.br'.format(self.path),
3064 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003065 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003066 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003067
3068 new_data_name = '{}.new.dat.br'.format(self.partition)
3069 ZipWrite(output_zip,
3070 '{}.new.dat.br'.format(self.path),
3071 new_data_name,
3072 compress_type=zipfile.ZIP_STORED)
3073 else:
3074 new_data_name = '{}.new.dat'.format(self.partition)
3075 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3076
Dan Albert8e0178d2015-01-27 15:53:15 -08003077 ZipWrite(output_zip,
3078 '{}.patch.dat'.format(self.path),
3079 '{}.patch.dat'.format(self.partition),
3080 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003081
Tianjie Xu209db462016-05-24 17:34:52 -07003082 if self.partition == "system":
3083 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3084 else:
3085 code = ErrorCode.VENDOR_UPDATE_FAILURE
3086
Yifan Hong10c530d2018-12-27 17:34:18 -08003087 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003088 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003089 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003090 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003091 device=self.device, partition=self.partition,
3092 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003093 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003094
Kelvin Zhang0876c412020-06-23 15:06:58 -04003095 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003096 data = source.ReadRangeSet(ranges)
3097 ctx = sha1()
3098
3099 for p in data:
3100 ctx.update(p)
3101
3102 return ctx.hexdigest()
3103
Kelvin Zhang0876c412020-06-23 15:06:58 -04003104 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003105 """Return the hash value for all zero blocks."""
3106 zero_block = '\x00' * 4096
3107 ctx = sha1()
3108 for _ in range(num_blocks):
3109 ctx.update(zero_block)
3110
3111 return ctx.hexdigest()
3112
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003113
Tianjie Xu41976c72019-07-03 13:57:01 -07003114# Expose these two classes to support vendor-specific scripts
3115DataImage = images.DataImage
3116EmptyImage = images.EmptyImage
3117
Tao Bao76def242017-11-21 09:25:31 -08003118
Doug Zongker96a57e72010-09-26 14:57:41 -07003119# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003120PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003121 "ext4": "EMMC",
3122 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003123 "f2fs": "EMMC",
3124 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003125}
Doug Zongker96a57e72010-09-26 14:57:41 -07003126
Kelvin Zhang0876c412020-06-23 15:06:58 -04003127
Yifan Hongbdb32012020-05-07 12:38:53 -07003128def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3129 """
3130 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3131 backwards compatibility. It aborts if the fstab entry has slotselect option
3132 (unless check_no_slot is explicitly set to False).
3133 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003134 fstab = info["fstab"]
3135 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003136 if check_no_slot:
3137 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003138 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003139 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3140 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003141 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003142
3143
Yifan Hongbdb32012020-05-07 12:38:53 -07003144def GetTypeAndDeviceExpr(mount_point, info):
3145 """
3146 Return the filesystem of the partition, and an edify expression that evaluates
3147 to the device at runtime.
3148 """
3149 fstab = info["fstab"]
3150 if fstab:
3151 p = fstab[mount_point]
3152 device_expr = '"%s"' % fstab[mount_point].device
3153 if p.slotselect:
3154 device_expr = 'add_slot_suffix(%s)' % device_expr
3155 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003156 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003157
3158
3159def GetEntryForDevice(fstab, device):
3160 """
3161 Returns:
3162 The first entry in fstab whose device is the given value.
3163 """
3164 if not fstab:
3165 return None
3166 for mount_point in fstab:
3167 if fstab[mount_point].device == device:
3168 return fstab[mount_point]
3169 return None
3170
Kelvin Zhang0876c412020-06-23 15:06:58 -04003171
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003172def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003173 """Parses and converts a PEM-encoded certificate into DER-encoded.
3174
3175 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3176
3177 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003178 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003179 """
3180 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003181 save = False
3182 for line in data.split("\n"):
3183 if "--END CERTIFICATE--" in line:
3184 break
3185 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003186 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003187 if "--BEGIN CERTIFICATE--" in line:
3188 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003189 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003190 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003191
Tao Bao04e1f012018-02-04 12:13:35 -08003192
3193def ExtractPublicKey(cert):
3194 """Extracts the public key (PEM-encoded) from the given certificate file.
3195
3196 Args:
3197 cert: The certificate filename.
3198
3199 Returns:
3200 The public key string.
3201
3202 Raises:
3203 AssertionError: On non-zero return from 'openssl'.
3204 """
3205 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3206 # While openssl 1.1 writes the key into the given filename followed by '-out',
3207 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3208 # stdout instead.
3209 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3210 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3211 pubkey, stderrdata = proc.communicate()
3212 assert proc.returncode == 0, \
3213 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3214 return pubkey
3215
3216
Tao Bao1ac886e2019-06-26 11:58:22 -07003217def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003218 """Extracts the AVB public key from the given public or private key.
3219
3220 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003221 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003222 key: The input key file, which should be PEM-encoded public or private key.
3223
3224 Returns:
3225 The path to the extracted AVB public key file.
3226 """
3227 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3228 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003229 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003230 return output
3231
3232
Doug Zongker412c02f2014-02-13 10:58:24 -08003233def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3234 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003235 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003236
Tao Bao6d5d6232018-03-09 17:04:42 -08003237 Most of the space in the boot and recovery images is just the kernel, which is
3238 identical for the two, so the resulting patch should be efficient. Add it to
3239 the output zip, along with a shell script that is run from init.rc on first
3240 boot to actually do the patching and install the new recovery image.
3241
3242 Args:
3243 input_dir: The top-level input directory of the target-files.zip.
3244 output_sink: The callback function that writes the result.
3245 recovery_img: File object for the recovery image.
3246 boot_img: File objects for the boot image.
3247 info_dict: A dict returned by common.LoadInfoDict() on the input
3248 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003249 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003250 if info_dict is None:
3251 info_dict = OPTIONS.info_dict
3252
Tao Bao6d5d6232018-03-09 17:04:42 -08003253 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003254 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3255
3256 if board_uses_vendorimage:
3257 # In this case, the output sink is rooted at VENDOR
3258 recovery_img_path = "etc/recovery.img"
3259 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3260 sh_dir = "bin"
3261 else:
3262 # In this case the output sink is rooted at SYSTEM
3263 recovery_img_path = "vendor/etc/recovery.img"
3264 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3265 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003266
Tao Baof2cffbd2015-07-22 12:33:18 -07003267 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003268 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003269
3270 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003271 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003272 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003273 # With system-root-image, boot and recovery images will have mismatching
3274 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3275 # to handle such a case.
3276 if system_root_image:
3277 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003278 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003279 assert not os.path.exists(path)
3280 else:
3281 diff_program = ["imgdiff"]
3282 if os.path.exists(path):
3283 diff_program.append("-b")
3284 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003285 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003286 else:
3287 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003288
3289 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3290 _, _, patch = d.ComputePatch()
3291 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003292
Dan Albertebb19aa2015-03-27 19:11:53 -07003293 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003294 # The following GetTypeAndDevice()s need to use the path in the target
3295 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003296 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3297 check_no_slot=False)
3298 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3299 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003300 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003301 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003302
Tao Baof2cffbd2015-07-22 12:33:18 -07003303 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003304
3305 # Note that we use /vendor to refer to the recovery resources. This will
3306 # work for a separate vendor partition mounted at /vendor or a
3307 # /system/vendor subdirectory on the system partition, for which init will
3308 # create a symlink from /vendor to /system/vendor.
3309
3310 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003311if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3312 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003313 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003314 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3315 log -t recovery "Installing new recovery image: succeeded" || \\
3316 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003317else
3318 log -t recovery "Recovery image already installed"
3319fi
3320""" % {'type': recovery_type,
3321 'device': recovery_device,
3322 'sha1': recovery_img.sha1,
3323 'size': recovery_img.size}
3324 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003325 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003326if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3327 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003328 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003329 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3330 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3331 log -t recovery "Installing new recovery image: succeeded" || \\
3332 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003333else
3334 log -t recovery "Recovery image already installed"
3335fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003336""" % {'boot_size': boot_img.size,
3337 'boot_sha1': boot_img.sha1,
3338 'recovery_size': recovery_img.size,
3339 'recovery_sha1': recovery_img.sha1,
3340 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003341 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003342 'recovery_type': recovery_type,
3343 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003344 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003345
Bill Peckhame868aec2019-09-17 17:06:47 -07003346 # The install script location moved from /system/etc to /system/bin in the L
3347 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3348 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003349
Tao Bao32fcdab2018-10-12 10:30:39 -07003350 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003351
Tao Baoda30cfa2017-12-01 16:19:46 -08003352 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003353
3354
3355class DynamicPartitionUpdate(object):
3356 def __init__(self, src_group=None, tgt_group=None, progress=None,
3357 block_difference=None):
3358 self.src_group = src_group
3359 self.tgt_group = tgt_group
3360 self.progress = progress
3361 self.block_difference = block_difference
3362
3363 @property
3364 def src_size(self):
3365 if not self.block_difference:
3366 return 0
3367 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3368
3369 @property
3370 def tgt_size(self):
3371 if not self.block_difference:
3372 return 0
3373 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3374
3375 @staticmethod
3376 def _GetSparseImageSize(img):
3377 if not img:
3378 return 0
3379 return img.blocksize * img.total_blocks
3380
3381
3382class DynamicGroupUpdate(object):
3383 def __init__(self, src_size=None, tgt_size=None):
3384 # None: group does not exist. 0: no size limits.
3385 self.src_size = src_size
3386 self.tgt_size = tgt_size
3387
3388
3389class DynamicPartitionsDifference(object):
3390 def __init__(self, info_dict, block_diffs, progress_dict=None,
3391 source_info_dict=None):
3392 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003393 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003394
3395 self._remove_all_before_apply = False
3396 if source_info_dict is None:
3397 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003398 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003399
Tao Baof1113e92019-06-18 12:10:14 -07003400 block_diff_dict = collections.OrderedDict(
3401 [(e.partition, e) for e in block_diffs])
3402
Yifan Hong10c530d2018-12-27 17:34:18 -08003403 assert len(block_diff_dict) == len(block_diffs), \
3404 "Duplicated BlockDifference object for {}".format(
3405 [partition for partition, count in
3406 collections.Counter(e.partition for e in block_diffs).items()
3407 if count > 1])
3408
Yifan Hong79997e52019-01-23 16:56:19 -08003409 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003410
3411 for p, block_diff in block_diff_dict.items():
3412 self._partition_updates[p] = DynamicPartitionUpdate()
3413 self._partition_updates[p].block_difference = block_diff
3414
3415 for p, progress in progress_dict.items():
3416 if p in self._partition_updates:
3417 self._partition_updates[p].progress = progress
3418
3419 tgt_groups = shlex.split(info_dict.get(
3420 "super_partition_groups", "").strip())
3421 src_groups = shlex.split(source_info_dict.get(
3422 "super_partition_groups", "").strip())
3423
3424 for g in tgt_groups:
3425 for p in shlex.split(info_dict.get(
3426 "super_%s_partition_list" % g, "").strip()):
3427 assert p in self._partition_updates, \
3428 "{} is in target super_{}_partition_list but no BlockDifference " \
3429 "object is provided.".format(p, g)
3430 self._partition_updates[p].tgt_group = g
3431
3432 for g in src_groups:
3433 for p in shlex.split(source_info_dict.get(
3434 "super_%s_partition_list" % g, "").strip()):
3435 assert p in self._partition_updates, \
3436 "{} is in source super_{}_partition_list but no BlockDifference " \
3437 "object is provided.".format(p, g)
3438 self._partition_updates[p].src_group = g
3439
Yifan Hong45433e42019-01-18 13:55:25 -08003440 target_dynamic_partitions = set(shlex.split(info_dict.get(
3441 "dynamic_partition_list", "").strip()))
3442 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3443 if u.tgt_size)
3444 assert block_diffs_with_target == target_dynamic_partitions, \
3445 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3446 list(target_dynamic_partitions), list(block_diffs_with_target))
3447
3448 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3449 "dynamic_partition_list", "").strip()))
3450 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3451 if u.src_size)
3452 assert block_diffs_with_source == source_dynamic_partitions, \
3453 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3454 list(source_dynamic_partitions), list(block_diffs_with_source))
3455
Yifan Hong10c530d2018-12-27 17:34:18 -08003456 if self._partition_updates:
3457 logger.info("Updating dynamic partitions %s",
3458 self._partition_updates.keys())
3459
Yifan Hong79997e52019-01-23 16:56:19 -08003460 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003461
3462 for g in tgt_groups:
3463 self._group_updates[g] = DynamicGroupUpdate()
3464 self._group_updates[g].tgt_size = int(info_dict.get(
3465 "super_%s_group_size" % g, "0").strip())
3466
3467 for g in src_groups:
3468 if g not in self._group_updates:
3469 self._group_updates[g] = DynamicGroupUpdate()
3470 self._group_updates[g].src_size = int(source_info_dict.get(
3471 "super_%s_group_size" % g, "0").strip())
3472
3473 self._Compute()
3474
3475 def WriteScript(self, script, output_zip, write_verify_script=False):
3476 script.Comment('--- Start patching dynamic partitions ---')
3477 for p, u in self._partition_updates.items():
3478 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3479 script.Comment('Patch partition %s' % p)
3480 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3481 write_verify_script=False)
3482
3483 op_list_path = MakeTempFile()
3484 with open(op_list_path, 'w') as f:
3485 for line in self._op_list:
3486 f.write('{}\n'.format(line))
3487
3488 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3489
3490 script.Comment('Update dynamic partition metadata')
3491 script.AppendExtra('assert(update_dynamic_partitions('
3492 'package_extract_file("dynamic_partitions_op_list")));')
3493
3494 if write_verify_script:
3495 for p, u in self._partition_updates.items():
3496 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3497 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003498 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003499
3500 for p, u in self._partition_updates.items():
3501 if u.tgt_size and u.src_size <= u.tgt_size:
3502 script.Comment('Patch partition %s' % p)
3503 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3504 write_verify_script=write_verify_script)
3505 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003506 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003507
3508 script.Comment('--- End patching dynamic partitions ---')
3509
3510 def _Compute(self):
3511 self._op_list = list()
3512
3513 def append(line):
3514 self._op_list.append(line)
3515
3516 def comment(line):
3517 self._op_list.append("# %s" % line)
3518
3519 if self._remove_all_before_apply:
3520 comment('Remove all existing dynamic partitions and groups before '
3521 'applying full OTA')
3522 append('remove_all_groups')
3523
3524 for p, u in self._partition_updates.items():
3525 if u.src_group and not u.tgt_group:
3526 append('remove %s' % p)
3527
3528 for p, u in self._partition_updates.items():
3529 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3530 comment('Move partition %s from %s to default' % (p, u.src_group))
3531 append('move %s default' % p)
3532
3533 for p, u in self._partition_updates.items():
3534 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3535 comment('Shrink partition %s from %d to %d' %
3536 (p, u.src_size, u.tgt_size))
3537 append('resize %s %s' % (p, u.tgt_size))
3538
3539 for g, u in self._group_updates.items():
3540 if u.src_size is not None and u.tgt_size is None:
3541 append('remove_group %s' % g)
3542 if (u.src_size is not None and u.tgt_size is not None and
3543 u.src_size > u.tgt_size):
3544 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3545 append('resize_group %s %d' % (g, u.tgt_size))
3546
3547 for g, u in self._group_updates.items():
3548 if u.src_size is None and u.tgt_size is not None:
3549 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3550 append('add_group %s %d' % (g, u.tgt_size))
3551 if (u.src_size is not None and u.tgt_size is not None and
3552 u.src_size < u.tgt_size):
3553 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3554 append('resize_group %s %d' % (g, u.tgt_size))
3555
3556 for p, u in self._partition_updates.items():
3557 if u.tgt_group and not u.src_group:
3558 comment('Add partition %s to group %s' % (p, u.tgt_group))
3559 append('add %s %s' % (p, u.tgt_group))
3560
3561 for p, u in self._partition_updates.items():
3562 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003563 comment('Grow partition %s from %d to %d' %
3564 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003565 append('resize %s %d' % (p, u.tgt_size))
3566
3567 for p, u in self._partition_updates.items():
3568 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3569 comment('Move partition %s from default to %s' %
3570 (p, u.tgt_group))
3571 append('move %s %s' % (p, u.tgt_group))