blob: 149deda2488d3044fba052adc42dafc95d9ec3c2 [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 Normand3351562020-10-29 12:33:11 -07001105def SharedUidPartitionViolations(uid_dict, partition_groups):
1106 """Checks for APK sharedUserIds that cross partition group boundaries.
1107
1108 This uses a single or merged build's shareduid_violation_modules.json
1109 output file, as generated by find_shareduid_violation.py or
1110 core/tasks/find-shareduid-violation.mk.
1111
1112 An error is defined as a sharedUserId that is found in a set of partitions
1113 that span more than one partition group.
1114
1115 Args:
1116 uid_dict: A dictionary created by using the standard json module to read a
1117 complete shareduid_violation_modules.json file.
1118 partition_groups: A list of groups, where each group is a list of
1119 partitions.
1120
1121 Returns:
1122 A list of error messages.
1123 """
1124 errors = []
1125 for uid, partitions in uid_dict.items():
1126 found_in_groups = [
1127 group for group in partition_groups
1128 if set(partitions.keys()) & set(group)
1129 ]
1130 if len(found_in_groups) > 1:
1131 errors.append(
1132 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1133 % (uid, ",".join(sorted(partitions.keys()))))
1134 return errors
1135
1136
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001137def AppendAVBSigningArgs(cmd, partition):
1138 """Append signing arguments for avbtool."""
1139 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1140 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001141 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1142 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1143 if os.path.exists(new_key_path):
1144 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001145 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1146 if key_path and algorithm:
1147 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001148 avb_salt = OPTIONS.info_dict.get("avb_salt")
1149 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001150 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001151 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001152
1153
Tao Bao765668f2019-10-04 22:03:00 -07001154def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001155 """Returns the VBMeta arguments for partition.
1156
1157 It sets up the VBMeta argument by including the partition descriptor from the
1158 given 'image', or by configuring the partition as a chained partition.
1159
1160 Args:
1161 partition: The name of the partition (e.g. "system").
1162 image: The path to the partition image.
1163 info_dict: A dict returned by common.LoadInfoDict(). Will use
1164 OPTIONS.info_dict if None has been given.
1165
1166 Returns:
1167 A list of VBMeta arguments.
1168 """
1169 if info_dict is None:
1170 info_dict = OPTIONS.info_dict
1171
1172 # Check if chain partition is used.
1173 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001174 if not key_path:
1175 return ["--include_descriptors_from_image", image]
1176
1177 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1178 # into vbmeta.img. The recovery image will be configured on an independent
1179 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1180 # See details at
1181 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001182 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001183 return []
1184
1185 # Otherwise chain the partition into vbmeta.
1186 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1187 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001188
1189
Tao Bao02a08592018-07-22 12:40:45 -07001190def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1191 """Constructs and returns the arg to build or verify a chained partition.
1192
1193 Args:
1194 partition: The partition name.
1195 info_dict: The info dict to look up the key info and rollback index
1196 location.
1197 key: The key to be used for building or verifying the partition. Defaults to
1198 the key listed in info_dict.
1199
1200 Returns:
1201 A string of form "partition:rollback_index_location:key" that can be used to
1202 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001203 """
1204 if key is None:
1205 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001206 if key and not os.path.exists(key) and OPTIONS.search_path:
1207 new_key_path = os.path.join(OPTIONS.search_path, key)
1208 if os.path.exists(new_key_path):
1209 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001210 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001211 rollback_index_location = info_dict[
1212 "avb_" + partition + "_rollback_index_location"]
1213 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1214
1215
Tianjie20dd8f22020-04-19 15:51:16 -07001216def ConstructAftlMakeImageCommands(output_image):
1217 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001218
1219 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001220 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001221 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1222 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1223 'No AFTL manufacturer key provided.'
1224
1225 vbmeta_image = MakeTempFile()
1226 os.rename(output_image, vbmeta_image)
1227 build_info = BuildInfo(OPTIONS.info_dict)
1228 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001229 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001230 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001231 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001232 "--vbmeta_image_path", vbmeta_image,
1233 "--output", output_image,
1234 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001235 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001236 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1237 "--algorithm", "SHA256_RSA4096",
1238 "--padding", "4096"]
1239 if OPTIONS.aftl_signer_helper:
1240 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001241 return aftl_cmd
1242
1243
1244def AddAftlInclusionProof(output_image):
1245 """Appends the aftl inclusion proof to the vbmeta image."""
1246
1247 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001248 RunAndCheckOutput(aftl_cmd)
1249
1250 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1251 output_image, '--transparency_log_pub_keys',
1252 OPTIONS.aftl_key_path]
1253 RunAndCheckOutput(verify_cmd)
1254
1255
Daniel Norman276f0622019-07-26 14:13:51 -07001256def BuildVBMeta(image_path, partitions, name, needed_partitions):
1257 """Creates a VBMeta image.
1258
1259 It generates the requested VBMeta image. The requested image could be for
1260 top-level or chained VBMeta image, which is determined based on the name.
1261
1262 Args:
1263 image_path: The output path for the new VBMeta image.
1264 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001265 values. Only valid partition names are accepted, as partitions listed
1266 in common.AVB_PARTITIONS and custom partitions listed in
1267 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001268 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1269 needed_partitions: Partitions whose descriptors should be included into the
1270 generated VBMeta image.
1271
1272 Raises:
1273 AssertionError: On invalid input args.
1274 """
1275 avbtool = OPTIONS.info_dict["avb_avbtool"]
1276 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1277 AppendAVBSigningArgs(cmd, name)
1278
Hongguang Chenf23364d2020-04-27 18:36:36 -07001279 custom_partitions = OPTIONS.info_dict.get(
1280 "avb_custom_images_partition_list", "").strip().split()
1281
Daniel Norman276f0622019-07-26 14:13:51 -07001282 for partition, path in partitions.items():
1283 if partition not in needed_partitions:
1284 continue
1285 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001286 partition in AVB_VBMETA_PARTITIONS or
1287 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001288 'Unknown partition: {}'.format(partition)
1289 assert os.path.exists(path), \
1290 'Failed to find {} for {}'.format(path, partition)
1291 cmd.extend(GetAvbPartitionArg(partition, path))
1292
1293 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1294 if args and args.strip():
1295 split_args = shlex.split(args)
1296 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001297 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001298 # as a path relative to source tree, which may not be available at the
1299 # same location when running this script (we have the input target_files
1300 # zip only). For such cases, we additionally scan other locations (e.g.
1301 # IMAGES/, RADIO/, etc) before bailing out.
1302 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001303 chained_image = split_args[index + 1]
1304 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001305 continue
1306 found = False
1307 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1308 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001309 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001310 if os.path.exists(alt_path):
1311 split_args[index + 1] = alt_path
1312 found = True
1313 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001314 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001315 cmd.extend(split_args)
1316
1317 RunAndCheckOutput(cmd)
1318
Tianjie Xueaed60c2020-03-12 00:33:28 -07001319 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001320 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001321 AddAftlInclusionProof(image_path)
1322
Daniel Norman276f0622019-07-26 14:13:51 -07001323
J. Avila98cd4cc2020-06-10 20:09:10 +00001324def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001325 ramdisk_img = tempfile.NamedTemporaryFile()
1326
1327 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1328 cmd = ["mkbootfs", "-f", fs_config_file,
1329 os.path.join(sourcedir, "RAMDISK")]
1330 else:
1331 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1332 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001333 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001334 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001335 stdout=ramdisk_img.file.fileno())
1336 else:
1337 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001338
1339 p2.wait()
1340 p1.wait()
1341 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001342 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001343
1344 return ramdisk_img
1345
1346
Steve Muckle9793cf62020-04-08 18:27:00 -07001347def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001348 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001349 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001350
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001351 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001352 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1353 we are building a two-step special image (i.e. building a recovery image to
1354 be loaded into /boot in two-step OTAs).
1355
1356 Return the image data, or None if sourcedir does not appear to contains files
1357 for building the requested image.
1358 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001359
Yifan Hong63c5ca12020-10-08 11:54:02 -07001360 if info_dict is None:
1361 info_dict = OPTIONS.info_dict
1362
Steve Muckle9793cf62020-04-08 18:27:00 -07001363 # "boot" or "recovery", without extension.
1364 partition_name = os.path.basename(sourcedir).lower()
1365
Yifan Hong63c5ca12020-10-08 11:54:02 -07001366 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001367 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001368 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1369 logger.info("Excluded kernel binary from recovery image.")
1370 else:
1371 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001372 else:
1373 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001374 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001375 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001376 return None
1377
1378 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001379 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001380
Doug Zongkereef39442009-04-02 12:14:19 -07001381 img = tempfile.NamedTemporaryFile()
1382
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001383 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001384 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1385 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001386
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001387 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1388 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1389
Yifan Hong63c5ca12020-10-08 11:54:02 -07001390 cmd = [mkbootimg]
1391 if kernel:
1392 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001393
Benoit Fradina45a8682014-07-14 21:00:43 +02001394 fn = os.path.join(sourcedir, "second")
1395 if os.access(fn, os.F_OK):
1396 cmd.append("--second")
1397 cmd.append(fn)
1398
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001399 fn = os.path.join(sourcedir, "dtb")
1400 if os.access(fn, os.F_OK):
1401 cmd.append("--dtb")
1402 cmd.append(fn)
1403
Doug Zongker171f1cd2009-06-15 22:36:37 -07001404 fn = os.path.join(sourcedir, "cmdline")
1405 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001406 cmd.append("--cmdline")
1407 cmd.append(open(fn).read().rstrip("\n"))
1408
1409 fn = os.path.join(sourcedir, "base")
1410 if os.access(fn, os.F_OK):
1411 cmd.append("--base")
1412 cmd.append(open(fn).read().rstrip("\n"))
1413
Ying Wang4de6b5b2010-08-25 14:29:34 -07001414 fn = os.path.join(sourcedir, "pagesize")
1415 if os.access(fn, os.F_OK):
1416 cmd.append("--pagesize")
1417 cmd.append(open(fn).read().rstrip("\n"))
1418
Steve Mucklef84668e2020-03-16 19:13:46 -07001419 if partition_name == "recovery":
1420 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301421 if not args:
1422 # Fall back to "mkbootimg_args" for recovery image
1423 # in case "recovery_mkbootimg_args" is not set.
1424 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001425 else:
1426 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001427 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001428 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001429
Tao Bao76def242017-11-21 09:25:31 -08001430 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001431 if args and args.strip():
1432 cmd.extend(shlex.split(args))
1433
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001434 if has_ramdisk:
1435 cmd.extend(["--ramdisk", ramdisk_img.name])
1436
Tao Baod95e9fd2015-03-29 23:07:41 -07001437 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001438 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001439 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001440 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001441 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001442 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001443
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001444 if partition_name == "recovery":
1445 if info_dict.get("include_recovery_dtbo") == "true":
1446 fn = os.path.join(sourcedir, "recovery_dtbo")
1447 cmd.extend(["--recovery_dtbo", fn])
1448 if info_dict.get("include_recovery_acpio") == "true":
1449 fn = os.path.join(sourcedir, "recovery_acpio")
1450 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001451
Tao Bao986ee862018-10-04 15:46:16 -07001452 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001453
Tao Bao76def242017-11-21 09:25:31 -08001454 if (info_dict.get("boot_signer") == "true" and
1455 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001456 # Hard-code the path as "/boot" for two-step special recovery image (which
1457 # will be loaded into /boot during the two-step OTA).
1458 if two_step_image:
1459 path = "/boot"
1460 else:
Tao Baobf70c312017-07-11 17:27:55 -07001461 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001462 cmd = [OPTIONS.boot_signer_path]
1463 cmd.extend(OPTIONS.boot_signer_args)
1464 cmd.extend([path, img.name,
1465 info_dict["verity_key"] + ".pk8",
1466 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001467 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001468
Tao Baod95e9fd2015-03-29 23:07:41 -07001469 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001470 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001471 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001472 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001473 # We have switched from the prebuilt futility binary to using the tool
1474 # (futility-host) built from the source. Override the setting in the old
1475 # TF.zip.
1476 futility = info_dict["futility"]
1477 if futility.startswith("prebuilts/"):
1478 futility = "futility-host"
1479 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001480 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001481 info_dict["vboot_key"] + ".vbprivk",
1482 info_dict["vboot_subkey"] + ".vbprivk",
1483 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001484 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001485 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001486
Tao Baof3282b42015-04-01 11:21:55 -07001487 # Clean up the temp files.
1488 img_unsigned.close()
1489 img_keyblock.close()
1490
David Zeuthen8fecb282017-12-01 16:24:01 -05001491 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001492 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001493 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001494 if partition_name == "recovery":
1495 part_size = info_dict["recovery_size"]
1496 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001497 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001498 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001499 "--partition_size", str(part_size), "--partition_name",
1500 partition_name]
1501 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001502 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001503 if args and args.strip():
1504 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001505 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001506
1507 img.seek(os.SEEK_SET, 0)
1508 data = img.read()
1509
1510 if has_ramdisk:
1511 ramdisk_img.close()
1512 img.close()
1513
1514 return data
1515
1516
Doug Zongkerd5131602012-08-02 14:46:42 -07001517def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001518 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001519 """Return a File object with the desired bootable image.
1520
1521 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1522 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1523 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001524
Doug Zongker55d93282011-01-25 17:03:34 -08001525 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1526 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001527 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001528 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001529
1530 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1531 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001532 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001533 return File.FromLocalFile(name, prebuilt_path)
1534
Tao Bao32fcdab2018-10-12 10:30:39 -07001535 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001536
1537 if info_dict is None:
1538 info_dict = OPTIONS.info_dict
1539
1540 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001541 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1542 # for recovery.
1543 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1544 prebuilt_name != "boot.img" or
1545 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001546
Doug Zongker6f1d0312014-08-22 08:07:12 -07001547 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001548 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001549 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001550 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001551 if data:
1552 return File(name, data)
1553 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001554
Doug Zongkereef39442009-04-02 12:14:19 -07001555
Steve Mucklee1b10862019-07-10 10:49:37 -07001556def _BuildVendorBootImage(sourcedir, info_dict=None):
1557 """Build a vendor boot image from the specified sourcedir.
1558
1559 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1560 turn them into a vendor boot image.
1561
1562 Return the image data, or None if sourcedir does not appear to contains files
1563 for building the requested image.
1564 """
1565
1566 if info_dict is None:
1567 info_dict = OPTIONS.info_dict
1568
1569 img = tempfile.NamedTemporaryFile()
1570
J. Avila98cd4cc2020-06-10 20:09:10 +00001571 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1572 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001573
1574 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1575 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1576
1577 cmd = [mkbootimg]
1578
1579 fn = os.path.join(sourcedir, "dtb")
1580 if os.access(fn, os.F_OK):
1581 cmd.append("--dtb")
1582 cmd.append(fn)
1583
1584 fn = os.path.join(sourcedir, "vendor_cmdline")
1585 if os.access(fn, os.F_OK):
1586 cmd.append("--vendor_cmdline")
1587 cmd.append(open(fn).read().rstrip("\n"))
1588
1589 fn = os.path.join(sourcedir, "base")
1590 if os.access(fn, os.F_OK):
1591 cmd.append("--base")
1592 cmd.append(open(fn).read().rstrip("\n"))
1593
1594 fn = os.path.join(sourcedir, "pagesize")
1595 if os.access(fn, os.F_OK):
1596 cmd.append("--pagesize")
1597 cmd.append(open(fn).read().rstrip("\n"))
1598
1599 args = info_dict.get("mkbootimg_args")
1600 if args and args.strip():
1601 cmd.extend(shlex.split(args))
1602
1603 args = info_dict.get("mkbootimg_version_args")
1604 if args and args.strip():
1605 cmd.extend(shlex.split(args))
1606
1607 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1608 cmd.extend(["--vendor_boot", img.name])
1609
1610 RunAndCheckOutput(cmd)
1611
1612 # AVB: if enabled, calculate and add hash.
1613 if info_dict.get("avb_enable") == "true":
1614 avbtool = info_dict["avb_avbtool"]
1615 part_size = info_dict["vendor_boot_size"]
1616 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001617 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001618 AppendAVBSigningArgs(cmd, "vendor_boot")
1619 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1620 if args and args.strip():
1621 cmd.extend(shlex.split(args))
1622 RunAndCheckOutput(cmd)
1623
1624 img.seek(os.SEEK_SET, 0)
1625 data = img.read()
1626
1627 ramdisk_img.close()
1628 img.close()
1629
1630 return data
1631
1632
1633def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1634 info_dict=None):
1635 """Return a File object with the desired vendor boot image.
1636
1637 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1638 the source files in 'unpack_dir'/'tree_subdir'."""
1639
1640 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1641 if os.path.exists(prebuilt_path):
1642 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1643 return File.FromLocalFile(name, prebuilt_path)
1644
1645 logger.info("building image from target_files %s...", tree_subdir)
1646
1647 if info_dict is None:
1648 info_dict = OPTIONS.info_dict
1649
Kelvin Zhang0876c412020-06-23 15:06:58 -04001650 data = _BuildVendorBootImage(
1651 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001652 if data:
1653 return File(name, data)
1654 return None
1655
1656
Narayan Kamatha07bf042017-08-14 14:49:21 +01001657def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001658 """Gunzips the given gzip compressed file to a given output file."""
1659 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001660 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001661 shutil.copyfileobj(in_file, out_file)
1662
1663
Tao Bao0ff15de2019-03-20 11:26:06 -07001664def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001665 """Unzips the archive to the given directory.
1666
1667 Args:
1668 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001669 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001670 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1671 archvie. Non-matching patterns will be filtered out. If there's no match
1672 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001673 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001674 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001675 if patterns is not None:
1676 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001677 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001678 names = input_zip.namelist()
1679 filtered = [
1680 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1681
1682 # There isn't any matching files. Don't unzip anything.
1683 if not filtered:
1684 return
1685 cmd.extend(filtered)
1686
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001687 RunAndCheckOutput(cmd)
1688
1689
Doug Zongker75f17362009-12-08 13:46:44 -08001690def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001691 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001692
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001693 Args:
1694 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1695 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1696
1697 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1698 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001699
Tao Bao1c830bf2017-12-25 10:43:47 -08001700 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001701 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001702 """
Doug Zongkereef39442009-04-02 12:14:19 -07001703
Tao Bao1c830bf2017-12-25 10:43:47 -08001704 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001705 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1706 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001707 UnzipToDir(m.group(1), tmp, pattern)
1708 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001709 filename = m.group(1)
1710 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001711 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001712
Tao Baodba59ee2018-01-09 13:21:02 -08001713 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001714
1715
Yifan Hong8a66a712019-04-04 15:37:57 -07001716def GetUserImage(which, tmpdir, input_zip,
1717 info_dict=None,
1718 allow_shared_blocks=None,
1719 hashtree_info_generator=None,
1720 reset_file_map=False):
1721 """Returns an Image object suitable for passing to BlockImageDiff.
1722
1723 This function loads the specified image from the given path. If the specified
1724 image is sparse, it also performs additional processing for OTA purpose. For
1725 example, it always adds block 0 to clobbered blocks list. It also detects
1726 files that cannot be reconstructed from the block list, for whom we should
1727 avoid applying imgdiff.
1728
1729 Args:
1730 which: The partition name.
1731 tmpdir: The directory that contains the prebuilt image and block map file.
1732 input_zip: The target-files ZIP archive.
1733 info_dict: The dict to be looked up for relevant info.
1734 allow_shared_blocks: If image is sparse, whether having shared blocks is
1735 allowed. If none, it is looked up from info_dict.
1736 hashtree_info_generator: If present and image is sparse, generates the
1737 hashtree_info for this sparse image.
1738 reset_file_map: If true and image is sparse, reset file map before returning
1739 the image.
1740 Returns:
1741 A Image object. If it is a sparse image and reset_file_map is False, the
1742 image will have file_map info loaded.
1743 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001744 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001745 info_dict = LoadInfoDict(input_zip)
1746
1747 is_sparse = info_dict.get("extfs_sparse_flag")
1748
1749 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1750 # shared blocks (i.e. some blocks will show up in multiple files' block
1751 # list). We can only allocate such shared blocks to the first "owner", and
1752 # disable imgdiff for all later occurrences.
1753 if allow_shared_blocks is None:
1754 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1755
1756 if is_sparse:
1757 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1758 hashtree_info_generator)
1759 if reset_file_map:
1760 img.ResetFileMap()
1761 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001762 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001763
1764
1765def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1766 """Returns a Image object suitable for passing to BlockImageDiff.
1767
1768 This function loads the specified non-sparse image from the given path.
1769
1770 Args:
1771 which: The partition name.
1772 tmpdir: The directory that contains the prebuilt image and block map file.
1773 Returns:
1774 A Image object.
1775 """
1776 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1777 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1778
1779 # The image and map files must have been created prior to calling
1780 # ota_from_target_files.py (since LMP).
1781 assert os.path.exists(path) and os.path.exists(mappath)
1782
Tianjie Xu41976c72019-07-03 13:57:01 -07001783 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1784
Yifan Hong8a66a712019-04-04 15:37:57 -07001785
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001786def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1787 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001788 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1789
1790 This function loads the specified sparse image from the given path, and
1791 performs additional processing for OTA purpose. For example, it always adds
1792 block 0 to clobbered blocks list. It also detects files that cannot be
1793 reconstructed from the block list, for whom we should avoid applying imgdiff.
1794
1795 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001796 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001797 tmpdir: The directory that contains the prebuilt image and block map file.
1798 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001799 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001800 hashtree_info_generator: If present, generates the hashtree_info for this
1801 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001802 Returns:
1803 A SparseImage object, with file_map info loaded.
1804 """
Tao Baoc765cca2018-01-31 17:32:40 -08001805 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1806 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1807
1808 # The image and map files must have been created prior to calling
1809 # ota_from_target_files.py (since LMP).
1810 assert os.path.exists(path) and os.path.exists(mappath)
1811
1812 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1813 # it to clobbered_blocks so that it will be written to the target
1814 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1815 clobbered_blocks = "0"
1816
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001817 image = sparse_img.SparseImage(
1818 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1819 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001820
1821 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1822 # if they contain all zeros. We can't reconstruct such a file from its block
1823 # list. Tag such entries accordingly. (Bug: 65213616)
1824 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001825 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001826 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001827 continue
1828
Tom Cherryd14b8952018-08-09 14:26:00 -07001829 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1830 # filename listed in system.map may contain an additional leading slash
1831 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1832 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001833 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001834
Tom Cherryd14b8952018-08-09 14:26:00 -07001835 # Special handling another case, where files not under /system
1836 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001837 if which == 'system' and not arcname.startswith('SYSTEM'):
1838 arcname = 'ROOT/' + arcname
1839
1840 assert arcname in input_zip.namelist(), \
1841 "Failed to find the ZIP entry for {}".format(entry)
1842
Tao Baoc765cca2018-01-31 17:32:40 -08001843 info = input_zip.getinfo(arcname)
1844 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001845
1846 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001847 # image, check the original block list to determine its completeness. Note
1848 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001849 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001850 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001851
Tao Baoc765cca2018-01-31 17:32:40 -08001852 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1853 ranges.extra['incomplete'] = True
1854
1855 return image
1856
1857
Doug Zongkereef39442009-04-02 12:14:19 -07001858def GetKeyPasswords(keylist):
1859 """Given a list of keys, prompt the user to enter passwords for
1860 those which require them. Return a {key: password} dict. password
1861 will be None if the key has no password."""
1862
Doug Zongker8ce7c252009-05-22 13:34:54 -07001863 no_passwords = []
1864 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001865 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001866 devnull = open("/dev/null", "w+b")
1867 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001868 # We don't need a password for things that aren't really keys.
1869 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001870 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001871 continue
1872
T.R. Fullhart37e10522013-03-18 10:31:26 -07001873 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001874 "-inform", "DER", "-nocrypt"],
1875 stdin=devnull.fileno(),
1876 stdout=devnull.fileno(),
1877 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001878 p.communicate()
1879 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001880 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001881 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001882 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001883 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1884 "-inform", "DER", "-passin", "pass:"],
1885 stdin=devnull.fileno(),
1886 stdout=devnull.fileno(),
1887 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001888 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001889 if p.returncode == 0:
1890 # Encrypted key with empty string as password.
1891 key_passwords[k] = ''
1892 elif stderr.startswith('Error decrypting key'):
1893 # Definitely encrypted key.
1894 # It would have said "Error reading key" if it didn't parse correctly.
1895 need_passwords.append(k)
1896 else:
1897 # Potentially, a type of key that openssl doesn't understand.
1898 # We'll let the routines in signapk.jar handle it.
1899 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001900 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001901
T.R. Fullhart37e10522013-03-18 10:31:26 -07001902 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001903 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001904 return key_passwords
1905
1906
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001907def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001908 """Gets the minSdkVersion declared in the APK.
1909
changho.shin0f125362019-07-08 10:59:00 +09001910 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001911 This can be both a decimal number (API Level) or a codename.
1912
1913 Args:
1914 apk_name: The APK filename.
1915
1916 Returns:
1917 The parsed SDK version string.
1918
1919 Raises:
1920 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001921 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001922 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001923 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001924 stderr=subprocess.PIPE)
1925 stdoutdata, stderrdata = proc.communicate()
1926 if proc.returncode != 0:
1927 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001928 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001929 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001930
Tao Baof47bf0f2018-03-21 23:28:51 -07001931 for line in stdoutdata.split("\n"):
1932 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001933 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1934 if m:
1935 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001936 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001937
1938
1939def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001940 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001941
Tao Baof47bf0f2018-03-21 23:28:51 -07001942 If minSdkVersion is set to a codename, it is translated to a number using the
1943 provided map.
1944
1945 Args:
1946 apk_name: The APK filename.
1947
1948 Returns:
1949 The parsed SDK version number.
1950
1951 Raises:
1952 ExternalError: On failing to get the min SDK version number.
1953 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001954 version = GetMinSdkVersion(apk_name)
1955 try:
1956 return int(version)
1957 except ValueError:
1958 # Not a decimal number. Codename?
1959 if version in codename_to_api_level_map:
1960 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04001961 raise ExternalError(
1962 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1963 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001964
1965
1966def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001967 codename_to_api_level_map=None, whole_file=False,
1968 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001969 """Sign the input_name zip/jar/apk, producing output_name. Use the
1970 given key and password (the latter may be None if the key does not
1971 have a password.
1972
Doug Zongker951495f2009-08-14 12:44:19 -07001973 If whole_file is true, use the "-w" option to SignApk to embed a
1974 signature that covers the whole file in the archive comment of the
1975 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001976
1977 min_api_level is the API Level (int) of the oldest platform this file may end
1978 up on. If not specified for an APK, the API Level is obtained by interpreting
1979 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1980
1981 codename_to_api_level_map is needed to translate the codename which may be
1982 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001983
1984 Caller may optionally specify extra args to be passed to SignApk, which
1985 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001986 """
Tao Bao76def242017-11-21 09:25:31 -08001987 if codename_to_api_level_map is None:
1988 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001989 if extra_signapk_args is None:
1990 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001991
Alex Klyubin9667b182015-12-10 13:38:50 -08001992 java_library_path = os.path.join(
1993 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1994
Tao Baoe95540e2016-11-08 12:08:53 -08001995 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1996 ["-Djava.library.path=" + java_library_path,
1997 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001998 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001999 if whole_file:
2000 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002001
2002 min_sdk_version = min_api_level
2003 if min_sdk_version is None:
2004 if not whole_file:
2005 min_sdk_version = GetMinSdkVersionInt(
2006 input_name, codename_to_api_level_map)
2007 if min_sdk_version is not None:
2008 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2009
T.R. Fullhart37e10522013-03-18 10:31:26 -07002010 cmd.extend([key + OPTIONS.public_key_suffix,
2011 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002012 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002013
Tao Bao73dd4f42018-10-04 16:25:33 -07002014 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002015 if password is not None:
2016 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002017 stdoutdata, _ = proc.communicate(password)
2018 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002019 raise ExternalError(
2020 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002021 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002022
Doug Zongkereef39442009-04-02 12:14:19 -07002023
Doug Zongker37974732010-09-16 17:44:38 -07002024def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002025 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002026
Tao Bao9dd909e2017-11-14 11:27:32 -08002027 For non-AVB images, raise exception if the data is too big. Print a warning
2028 if the data is nearing the maximum size.
2029
2030 For AVB images, the actual image size should be identical to the limit.
2031
2032 Args:
2033 data: A string that contains all the data for the partition.
2034 target: The partition name. The ".img" suffix is optional.
2035 info_dict: The dict to be looked up for relevant info.
2036 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002037 if target.endswith(".img"):
2038 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002039 mount_point = "/" + target
2040
Ying Wangf8824af2014-06-03 14:07:27 -07002041 fs_type = None
2042 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002043 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002044 if mount_point == "/userdata":
2045 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002046 p = info_dict["fstab"][mount_point]
2047 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002048 device = p.device
2049 if "/" in device:
2050 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002051 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002052 if not fs_type or not limit:
2053 return
Doug Zongkereef39442009-04-02 12:14:19 -07002054
Andrew Boie0f9aec82012-02-14 09:32:52 -08002055 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002056 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2057 # path.
2058 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2059 if size != limit:
2060 raise ExternalError(
2061 "Mismatching image size for %s: expected %d actual %d" % (
2062 target, limit, size))
2063 else:
2064 pct = float(size) * 100.0 / limit
2065 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2066 if pct >= 99.0:
2067 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002068
2069 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002070 logger.warning("\n WARNING: %s\n", msg)
2071 else:
2072 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002073
2074
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002075def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002076 """Parses the APK certs info from a given target-files zip.
2077
2078 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2079 tuple with the following elements: (1) a dictionary that maps packages to
2080 certs (based on the "certificate" and "private_key" attributes in the file;
2081 (2) a string representing the extension of compressed APKs in the target files
2082 (e.g ".gz", ".bro").
2083
2084 Args:
2085 tf_zip: The input target_files ZipFile (already open).
2086
2087 Returns:
2088 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2089 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2090 no compressed APKs.
2091 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002092 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002093 compressed_extension = None
2094
Tao Bao0f990332017-09-08 19:02:54 -07002095 # META/apkcerts.txt contains the info for _all_ the packages known at build
2096 # time. Filter out the ones that are not installed.
2097 installed_files = set()
2098 for name in tf_zip.namelist():
2099 basename = os.path.basename(name)
2100 if basename:
2101 installed_files.add(basename)
2102
Tao Baoda30cfa2017-12-01 16:19:46 -08002103 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002104 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002105 if not line:
2106 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002107 m = re.match(
2108 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002109 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2110 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002111 line)
2112 if not m:
2113 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002114
Tao Bao818ddf52018-01-05 11:17:34 -08002115 matches = m.groupdict()
2116 cert = matches["CERT"]
2117 privkey = matches["PRIVKEY"]
2118 name = matches["NAME"]
2119 this_compressed_extension = matches["COMPRESSED"]
2120
2121 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2122 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2123 if cert in SPECIAL_CERT_STRINGS and not privkey:
2124 certmap[name] = cert
2125 elif (cert.endswith(OPTIONS.public_key_suffix) and
2126 privkey.endswith(OPTIONS.private_key_suffix) and
2127 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2128 certmap[name] = cert[:-public_key_suffix_len]
2129 else:
2130 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2131
2132 if not this_compressed_extension:
2133 continue
2134
2135 # Only count the installed files.
2136 filename = name + '.' + this_compressed_extension
2137 if filename not in installed_files:
2138 continue
2139
2140 # Make sure that all the values in the compression map have the same
2141 # extension. We don't support multiple compression methods in the same
2142 # system image.
2143 if compressed_extension:
2144 if this_compressed_extension != compressed_extension:
2145 raise ValueError(
2146 "Multiple compressed extensions: {} vs {}".format(
2147 compressed_extension, this_compressed_extension))
2148 else:
2149 compressed_extension = this_compressed_extension
2150
2151 return (certmap,
2152 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002153
2154
Doug Zongkereef39442009-04-02 12:14:19 -07002155COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002156Global options
2157
2158 -p (--path) <dir>
2159 Prepend <dir>/bin to the list of places to search for binaries run by this
2160 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002161
Doug Zongker05d3dea2009-06-22 11:32:31 -07002162 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002163 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002164
Tao Bao30df8b42018-04-23 15:32:53 -07002165 -x (--extra) <key=value>
2166 Add a key/value pair to the 'extras' dict, which device-specific extension
2167 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002168
Doug Zongkereef39442009-04-02 12:14:19 -07002169 -v (--verbose)
2170 Show command lines being executed.
2171
2172 -h (--help)
2173 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002174
2175 --logfile <file>
2176 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002177"""
2178
Kelvin Zhang0876c412020-06-23 15:06:58 -04002179
Doug Zongkereef39442009-04-02 12:14:19 -07002180def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002181 print(docstring.rstrip("\n"))
2182 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002183
2184
2185def ParseOptions(argv,
2186 docstring,
2187 extra_opts="", extra_long_opts=(),
2188 extra_option_handler=None):
2189 """Parse the options in argv and return any arguments that aren't
2190 flags. docstring is the calling module's docstring, to be displayed
2191 for errors and -h. extra_opts and extra_long_opts are for flags
2192 defined by the caller, which are processed by passing them to
2193 extra_option_handler."""
2194
2195 try:
2196 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002197 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002198 ["help", "verbose", "path=", "signapk_path=",
2199 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002200 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002201 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2202 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002203 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2204 "aftl_key_path=", "aftl_manufacturer_key_path=",
2205 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002206 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002207 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002208 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002209 sys.exit(2)
2210
Doug Zongkereef39442009-04-02 12:14:19 -07002211 for o, a in opts:
2212 if o in ("-h", "--help"):
2213 Usage(docstring)
2214 sys.exit()
2215 elif o in ("-v", "--verbose"):
2216 OPTIONS.verbose = True
2217 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002218 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002219 elif o in ("--signapk_path",):
2220 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002221 elif o in ("--signapk_shared_library_path",):
2222 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002223 elif o in ("--extra_signapk_args",):
2224 OPTIONS.extra_signapk_args = shlex.split(a)
2225 elif o in ("--java_path",):
2226 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002227 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002228 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002229 elif o in ("--android_jar_path",):
2230 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002231 elif o in ("--public_key_suffix",):
2232 OPTIONS.public_key_suffix = a
2233 elif o in ("--private_key_suffix",):
2234 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002235 elif o in ("--boot_signer_path",):
2236 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002237 elif o in ("--boot_signer_args",):
2238 OPTIONS.boot_signer_args = shlex.split(a)
2239 elif o in ("--verity_signer_path",):
2240 OPTIONS.verity_signer_path = a
2241 elif o in ("--verity_signer_args",):
2242 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002243 elif o in ("--aftl_tool_path",):
2244 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002245 elif o in ("--aftl_server",):
2246 OPTIONS.aftl_server = a
2247 elif o in ("--aftl_key_path",):
2248 OPTIONS.aftl_key_path = a
2249 elif o in ("--aftl_manufacturer_key_path",):
2250 OPTIONS.aftl_manufacturer_key_path = a
2251 elif o in ("--aftl_signer_helper",):
2252 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002253 elif o in ("-s", "--device_specific"):
2254 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002255 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002256 key, value = a.split("=", 1)
2257 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002258 elif o in ("--logfile",):
2259 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002260 else:
2261 if extra_option_handler is None or not extra_option_handler(o, a):
2262 assert False, "unknown option \"%s\"" % (o,)
2263
Doug Zongker85448772014-09-09 14:59:20 -07002264 if OPTIONS.search_path:
2265 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2266 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002267
2268 return args
2269
2270
Tao Bao4c851b12016-09-19 13:54:38 -07002271def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002272 """Make a temp file and add it to the list of things to be deleted
2273 when Cleanup() is called. Return the filename."""
2274 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2275 os.close(fd)
2276 OPTIONS.tempfiles.append(fn)
2277 return fn
2278
2279
Tao Bao1c830bf2017-12-25 10:43:47 -08002280def MakeTempDir(prefix='tmp', suffix=''):
2281 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2282
2283 Returns:
2284 The absolute pathname of the new directory.
2285 """
2286 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2287 OPTIONS.tempfiles.append(dir_name)
2288 return dir_name
2289
2290
Doug Zongkereef39442009-04-02 12:14:19 -07002291def Cleanup():
2292 for i in OPTIONS.tempfiles:
2293 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002294 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002295 else:
2296 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002297 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002298
2299
2300class PasswordManager(object):
2301 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002302 self.editor = os.getenv("EDITOR")
2303 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002304
2305 def GetPasswords(self, items):
2306 """Get passwords corresponding to each string in 'items',
2307 returning a dict. (The dict may have keys in addition to the
2308 values in 'items'.)
2309
2310 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2311 user edit that file to add more needed passwords. If no editor is
2312 available, or $ANDROID_PW_FILE isn't define, prompts the user
2313 interactively in the ordinary way.
2314 """
2315
2316 current = self.ReadFile()
2317
2318 first = True
2319 while True:
2320 missing = []
2321 for i in items:
2322 if i not in current or not current[i]:
2323 missing.append(i)
2324 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002325 if not missing:
2326 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002327
2328 for i in missing:
2329 current[i] = ""
2330
2331 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002332 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002333 if sys.version_info[0] >= 3:
2334 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002335 answer = raw_input("try to edit again? [y]> ").strip()
2336 if answer and answer[0] not in 'yY':
2337 raise RuntimeError("key passwords unavailable")
2338 first = False
2339
2340 current = self.UpdateAndReadFile(current)
2341
Kelvin Zhang0876c412020-06-23 15:06:58 -04002342 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002343 """Prompt the user to enter a value (password) for each key in
2344 'current' whose value is fales. Returns a new dict with all the
2345 values.
2346 """
2347 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002348 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002349 if v:
2350 result[k] = v
2351 else:
2352 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002353 result[k] = getpass.getpass(
2354 "Enter password for %s key> " % k).strip()
2355 if result[k]:
2356 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002357 return result
2358
2359 def UpdateAndReadFile(self, current):
2360 if not self.editor or not self.pwfile:
2361 return self.PromptResult(current)
2362
2363 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002364 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002365 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2366 f.write("# (Additional spaces are harmless.)\n\n")
2367
2368 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002369 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002370 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002371 f.write("[[[ %s ]]] %s\n" % (v, k))
2372 if not v and first_line is None:
2373 # position cursor on first line with no password.
2374 first_line = i + 4
2375 f.close()
2376
Tao Bao986ee862018-10-04 15:46:16 -07002377 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002378
2379 return self.ReadFile()
2380
2381 def ReadFile(self):
2382 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002383 if self.pwfile is None:
2384 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002385 try:
2386 f = open(self.pwfile, "r")
2387 for line in f:
2388 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002389 if not line or line[0] == '#':
2390 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002391 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2392 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002393 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002394 else:
2395 result[m.group(2)] = m.group(1)
2396 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002397 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002398 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002399 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002400 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002401
2402
Dan Albert8e0178d2015-01-27 15:53:15 -08002403def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2404 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002405
2406 # http://b/18015246
2407 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2408 # for files larger than 2GiB. We can work around this by adjusting their
2409 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2410 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2411 # it isn't clear to me exactly what circumstances cause this).
2412 # `zipfile.write()` must be used directly to work around this.
2413 #
2414 # This mess can be avoided if we port to python3.
2415 saved_zip64_limit = zipfile.ZIP64_LIMIT
2416 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2417
2418 if compress_type is None:
2419 compress_type = zip_file.compression
2420 if arcname is None:
2421 arcname = filename
2422
2423 saved_stat = os.stat(filename)
2424
2425 try:
2426 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2427 # file to be zipped and reset it when we're done.
2428 os.chmod(filename, perms)
2429
2430 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002431 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2432 # intentional. zip stores datetimes in local time without a time zone
2433 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2434 # in the zip archive.
2435 local_epoch = datetime.datetime.fromtimestamp(0)
2436 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002437 os.utime(filename, (timestamp, timestamp))
2438
2439 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2440 finally:
2441 os.chmod(filename, saved_stat.st_mode)
2442 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2443 zipfile.ZIP64_LIMIT = saved_zip64_limit
2444
2445
Tao Bao58c1b962015-05-20 09:32:18 -07002446def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002447 compress_type=None):
2448 """Wrap zipfile.writestr() function to work around the zip64 limit.
2449
2450 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2451 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2452 when calling crc32(bytes).
2453
2454 But it still works fine to write a shorter string into a large zip file.
2455 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2456 when we know the string won't be too long.
2457 """
2458
2459 saved_zip64_limit = zipfile.ZIP64_LIMIT
2460 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2461
2462 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2463 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002464 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002465 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002466 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002467 else:
Tao Baof3282b42015-04-01 11:21:55 -07002468 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002469 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2470 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2471 # such a case (since
2472 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2473 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2474 # permission bits. We follow the logic in Python 3 to get consistent
2475 # behavior between using the two versions.
2476 if not zinfo.external_attr:
2477 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002478
2479 # If compress_type is given, it overrides the value in zinfo.
2480 if compress_type is not None:
2481 zinfo.compress_type = compress_type
2482
Tao Bao58c1b962015-05-20 09:32:18 -07002483 # If perms is given, it has a priority.
2484 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002485 # If perms doesn't set the file type, mark it as a regular file.
2486 if perms & 0o770000 == 0:
2487 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002488 zinfo.external_attr = perms << 16
2489
Tao Baof3282b42015-04-01 11:21:55 -07002490 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002491 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2492
Dan Albert8b72aef2015-03-23 19:13:21 -07002493 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002494 zipfile.ZIP64_LIMIT = saved_zip64_limit
2495
2496
Tao Bao89d7ab22017-12-14 17:05:33 -08002497def ZipDelete(zip_filename, entries):
2498 """Deletes entries from a ZIP file.
2499
2500 Since deleting entries from a ZIP file is not supported, it shells out to
2501 'zip -d'.
2502
2503 Args:
2504 zip_filename: The name of the ZIP file.
2505 entries: The name of the entry, or the list of names to be deleted.
2506
2507 Raises:
2508 AssertionError: In case of non-zero return from 'zip'.
2509 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002510 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002511 entries = [entries]
2512 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002513 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002514
2515
Tao Baof3282b42015-04-01 11:21:55 -07002516def ZipClose(zip_file):
2517 # http://b/18015246
2518 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2519 # central directory.
2520 saved_zip64_limit = zipfile.ZIP64_LIMIT
2521 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2522
2523 zip_file.close()
2524
2525 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002526
2527
2528class DeviceSpecificParams(object):
2529 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002530
Doug Zongker05d3dea2009-06-22 11:32:31 -07002531 def __init__(self, **kwargs):
2532 """Keyword arguments to the constructor become attributes of this
2533 object, which is passed to all functions in the device-specific
2534 module."""
Tao Bao38884282019-07-10 22:20:56 -07002535 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002536 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002537 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002538
2539 if self.module is None:
2540 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002541 if not path:
2542 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002543 try:
2544 if os.path.isdir(path):
2545 info = imp.find_module("releasetools", [path])
2546 else:
2547 d, f = os.path.split(path)
2548 b, x = os.path.splitext(f)
2549 if x == ".py":
2550 f = b
2551 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002552 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002553 self.module = imp.load_module("device_specific", *info)
2554 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002555 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002556
2557 def _DoCall(self, function_name, *args, **kwargs):
2558 """Call the named function in the device-specific module, passing
2559 the given args and kwargs. The first argument to the call will be
2560 the DeviceSpecific object itself. If there is no module, or the
2561 module does not define the function, return the value of the
2562 'default' kwarg (which itself defaults to None)."""
2563 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002564 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002565 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2566
2567 def FullOTA_Assertions(self):
2568 """Called after emitting the block of assertions at the top of a
2569 full OTA package. Implementations can add whatever additional
2570 assertions they like."""
2571 return self._DoCall("FullOTA_Assertions")
2572
Doug Zongkere5ff5902012-01-17 10:55:37 -08002573 def FullOTA_InstallBegin(self):
2574 """Called at the start of full OTA installation."""
2575 return self._DoCall("FullOTA_InstallBegin")
2576
Yifan Hong10c530d2018-12-27 17:34:18 -08002577 def FullOTA_GetBlockDifferences(self):
2578 """Called during full OTA installation and verification.
2579 Implementation should return a list of BlockDifference objects describing
2580 the update on each additional partitions.
2581 """
2582 return self._DoCall("FullOTA_GetBlockDifferences")
2583
Doug Zongker05d3dea2009-06-22 11:32:31 -07002584 def FullOTA_InstallEnd(self):
2585 """Called at the end of full OTA installation; typically this is
2586 used to install the image for the device's baseband processor."""
2587 return self._DoCall("FullOTA_InstallEnd")
2588
2589 def IncrementalOTA_Assertions(self):
2590 """Called after emitting the block of assertions at the top of an
2591 incremental OTA package. Implementations can add whatever
2592 additional assertions they like."""
2593 return self._DoCall("IncrementalOTA_Assertions")
2594
Doug Zongkere5ff5902012-01-17 10:55:37 -08002595 def IncrementalOTA_VerifyBegin(self):
2596 """Called at the start of the verification phase of incremental
2597 OTA installation; additional checks can be placed here to abort
2598 the script before any changes are made."""
2599 return self._DoCall("IncrementalOTA_VerifyBegin")
2600
Doug Zongker05d3dea2009-06-22 11:32:31 -07002601 def IncrementalOTA_VerifyEnd(self):
2602 """Called at the end of the verification phase of incremental OTA
2603 installation; additional checks can be placed here to abort the
2604 script before any changes are made."""
2605 return self._DoCall("IncrementalOTA_VerifyEnd")
2606
Doug Zongkere5ff5902012-01-17 10:55:37 -08002607 def IncrementalOTA_InstallBegin(self):
2608 """Called at the start of incremental OTA installation (after
2609 verification is complete)."""
2610 return self._DoCall("IncrementalOTA_InstallBegin")
2611
Yifan Hong10c530d2018-12-27 17:34:18 -08002612 def IncrementalOTA_GetBlockDifferences(self):
2613 """Called during incremental OTA installation and verification.
2614 Implementation should return a list of BlockDifference objects describing
2615 the update on each additional partitions.
2616 """
2617 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2618
Doug Zongker05d3dea2009-06-22 11:32:31 -07002619 def IncrementalOTA_InstallEnd(self):
2620 """Called at the end of incremental OTA installation; typically
2621 this is used to install the image for the device's baseband
2622 processor."""
2623 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002624
Tao Bao9bc6bb22015-11-09 16:58:28 -08002625 def VerifyOTA_Assertions(self):
2626 return self._DoCall("VerifyOTA_Assertions")
2627
Tao Bao76def242017-11-21 09:25:31 -08002628
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002629class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002630 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002631 self.name = name
2632 self.data = data
2633 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002634 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002635 self.sha1 = sha1(data).hexdigest()
2636
2637 @classmethod
2638 def FromLocalFile(cls, name, diskname):
2639 f = open(diskname, "rb")
2640 data = f.read()
2641 f.close()
2642 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002643
2644 def WriteToTemp(self):
2645 t = tempfile.NamedTemporaryFile()
2646 t.write(self.data)
2647 t.flush()
2648 return t
2649
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002650 def WriteToDir(self, d):
2651 with open(os.path.join(d, self.name), "wb") as fp:
2652 fp.write(self.data)
2653
Geremy Condra36bd3652014-02-06 19:45:10 -08002654 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002655 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002656
Tao Bao76def242017-11-21 09:25:31 -08002657
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002658DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002659 ".gz": "imgdiff",
2660 ".zip": ["imgdiff", "-z"],
2661 ".jar": ["imgdiff", "-z"],
2662 ".apk": ["imgdiff", "-z"],
2663 ".img": "imgdiff",
2664}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002665
Tao Bao76def242017-11-21 09:25:31 -08002666
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002667class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002668 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002669 self.tf = tf
2670 self.sf = sf
2671 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002672 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002673
2674 def ComputePatch(self):
2675 """Compute the patch (as a string of data) needed to turn sf into
2676 tf. Returns the same tuple as GetPatch()."""
2677
2678 tf = self.tf
2679 sf = self.sf
2680
Doug Zongker24cd2802012-08-14 16:36:15 -07002681 if self.diff_program:
2682 diff_program = self.diff_program
2683 else:
2684 ext = os.path.splitext(tf.name)[1]
2685 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002686
2687 ttemp = tf.WriteToTemp()
2688 stemp = sf.WriteToTemp()
2689
2690 ext = os.path.splitext(tf.name)[1]
2691
2692 try:
2693 ptemp = tempfile.NamedTemporaryFile()
2694 if isinstance(diff_program, list):
2695 cmd = copy.copy(diff_program)
2696 else:
2697 cmd = [diff_program]
2698 cmd.append(stemp.name)
2699 cmd.append(ttemp.name)
2700 cmd.append(ptemp.name)
2701 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002702 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002703
Doug Zongkerf8340082014-08-05 10:39:37 -07002704 def run():
2705 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002706 if e:
2707 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002708 th = threading.Thread(target=run)
2709 th.start()
2710 th.join(timeout=300) # 5 mins
2711 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002712 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002713 p.terminate()
2714 th.join(5)
2715 if th.is_alive():
2716 p.kill()
2717 th.join()
2718
Tianjie Xua2a9f992018-01-05 15:15:54 -08002719 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002720 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002721 self.patch = None
2722 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002723 diff = ptemp.read()
2724 finally:
2725 ptemp.close()
2726 stemp.close()
2727 ttemp.close()
2728
2729 self.patch = diff
2730 return self.tf, self.sf, self.patch
2731
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002732 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002733 """Returns a tuple of (target_file, source_file, patch_data).
2734
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002735 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002736 computing the patch failed.
2737 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002738 return self.tf, self.sf, self.patch
2739
2740
2741def ComputeDifferences(diffs):
2742 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002743 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002744
2745 # Do the largest files first, to try and reduce the long-pole effect.
2746 by_size = [(i.tf.size, i) for i in diffs]
2747 by_size.sort(reverse=True)
2748 by_size = [i[1] for i in by_size]
2749
2750 lock = threading.Lock()
2751 diff_iter = iter(by_size) # accessed under lock
2752
2753 def worker():
2754 try:
2755 lock.acquire()
2756 for d in diff_iter:
2757 lock.release()
2758 start = time.time()
2759 d.ComputePatch()
2760 dur = time.time() - start
2761 lock.acquire()
2762
2763 tf, sf, patch = d.GetPatch()
2764 if sf.name == tf.name:
2765 name = tf.name
2766 else:
2767 name = "%s (%s)" % (tf.name, sf.name)
2768 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002769 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002770 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002771 logger.info(
2772 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2773 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002774 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002775 except Exception:
2776 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002777 raise
2778
2779 # start worker threads; wait for them all to finish.
2780 threads = [threading.Thread(target=worker)
2781 for i in range(OPTIONS.worker_threads)]
2782 for th in threads:
2783 th.start()
2784 while threads:
2785 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002786
2787
Dan Albert8b72aef2015-03-23 19:13:21 -07002788class BlockDifference(object):
2789 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002790 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002791 self.tgt = tgt
2792 self.src = src
2793 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002794 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002795 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002796
Tao Baodd2a5892015-03-12 12:32:37 -07002797 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002798 version = max(
2799 int(i) for i in
2800 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002801 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002802 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002803
Tianjie Xu41976c72019-07-03 13:57:01 -07002804 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2805 version=self.version,
2806 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002807 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002808 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002809 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002810 self.touched_src_ranges = b.touched_src_ranges
2811 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002812
Yifan Hong10c530d2018-12-27 17:34:18 -08002813 # On devices with dynamic partitions, for new partitions,
2814 # src is None but OPTIONS.source_info_dict is not.
2815 if OPTIONS.source_info_dict is None:
2816 is_dynamic_build = OPTIONS.info_dict.get(
2817 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002818 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002819 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002820 is_dynamic_build = OPTIONS.source_info_dict.get(
2821 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002822 is_dynamic_source = partition in shlex.split(
2823 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002824
Yifan Hongbb2658d2019-01-25 12:30:58 -08002825 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002826 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2827
Yifan Hongbb2658d2019-01-25 12:30:58 -08002828 # For dynamic partitions builds, check partition list in both source
2829 # and target build because new partitions may be added, and existing
2830 # partitions may be removed.
2831 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2832
Yifan Hong10c530d2018-12-27 17:34:18 -08002833 if is_dynamic:
2834 self.device = 'map_partition("%s")' % partition
2835 else:
2836 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002837 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2838 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002839 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002840 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2841 OPTIONS.source_info_dict)
2842 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002843
Tao Baod8d14be2016-02-04 14:26:02 -08002844 @property
2845 def required_cache(self):
2846 return self._required_cache
2847
Tao Bao76def242017-11-21 09:25:31 -08002848 def WriteScript(self, script, output_zip, progress=None,
2849 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002850 if not self.src:
2851 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002852 script.Print("Patching %s image unconditionally..." % (self.partition,))
2853 else:
2854 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002855
Dan Albert8b72aef2015-03-23 19:13:21 -07002856 if progress:
2857 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002858 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002859
2860 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002861 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002862
Tao Bao9bc6bb22015-11-09 16:58:28 -08002863 def WriteStrictVerifyScript(self, script):
2864 """Verify all the blocks in the care_map, including clobbered blocks.
2865
2866 This differs from the WriteVerifyScript() function: a) it prints different
2867 error messages; b) it doesn't allow half-way updated images to pass the
2868 verification."""
2869
2870 partition = self.partition
2871 script.Print("Verifying %s..." % (partition,))
2872 ranges = self.tgt.care_map
2873 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002874 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002875 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2876 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002877 self.device, ranges_str,
2878 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002879 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002880 script.AppendExtra("")
2881
Tao Baod522bdc2016-04-12 15:53:16 -07002882 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002883 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002884
2885 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002886 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002887 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002888
2889 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002890 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002891 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002892 ranges = self.touched_src_ranges
2893 expected_sha1 = self.touched_src_sha1
2894 else:
2895 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2896 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002897
2898 # No blocks to be checked, skipping.
2899 if not ranges:
2900 return
2901
Tao Bao5ece99d2015-05-12 11:42:31 -07002902 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002903 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002904 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002905 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2906 '"%s.patch.dat")) then' % (
2907 self.device, ranges_str, expected_sha1,
2908 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002909 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002910 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002911
Tianjie Xufc3422a2015-12-15 11:53:59 -08002912 if self.version >= 4:
2913
2914 # Bug: 21124327
2915 # When generating incrementals for the system and vendor partitions in
2916 # version 4 or newer, explicitly check the first block (which contains
2917 # the superblock) of the partition to see if it's what we expect. If
2918 # this check fails, give an explicit log message about the partition
2919 # having been remounted R/W (the most likely explanation).
2920 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002921 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002922
2923 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002924 if partition == "system":
2925 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2926 else:
2927 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002928 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002929 'ifelse (block_image_recover({device}, "{ranges}") && '
2930 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002931 'package_extract_file("{partition}.transfer.list"), '
2932 '"{partition}.new.dat", "{partition}.patch.dat"), '
2933 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002934 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002935 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002936 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002937
Tao Baodd2a5892015-03-12 12:32:37 -07002938 # Abort the OTA update. Note that the incremental OTA cannot be applied
2939 # even if it may match the checksum of the target partition.
2940 # a) If version < 3, operations like move and erase will make changes
2941 # unconditionally and damage the partition.
2942 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002943 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002944 if partition == "system":
2945 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2946 else:
2947 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2948 script.AppendExtra((
2949 'abort("E%d: %s partition has unexpected contents");\n'
2950 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002951
Yifan Hong10c530d2018-12-27 17:34:18 -08002952 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002953 partition = self.partition
2954 script.Print('Verifying the updated %s image...' % (partition,))
2955 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2956 ranges = self.tgt.care_map
2957 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002958 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002959 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002960 self.device, ranges_str,
2961 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002962
2963 # Bug: 20881595
2964 # Verify that extended blocks are really zeroed out.
2965 if self.tgt.extended:
2966 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002967 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002968 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002969 self.device, ranges_str,
2970 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002971 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002972 if partition == "system":
2973 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2974 else:
2975 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002976 script.AppendExtra(
2977 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002978 ' abort("E%d: %s partition has unexpected non-zero contents after '
2979 'OTA update");\n'
2980 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002981 else:
2982 script.Print('Verified the updated %s image.' % (partition,))
2983
Tianjie Xu209db462016-05-24 17:34:52 -07002984 if partition == "system":
2985 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2986 else:
2987 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2988
Tao Bao5fcaaef2015-06-01 13:40:49 -07002989 script.AppendExtra(
2990 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002991 ' abort("E%d: %s partition has unexpected contents after OTA '
2992 'update");\n'
2993 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002994
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002995 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002996 ZipWrite(output_zip,
2997 '{}.transfer.list'.format(self.path),
2998 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002999
Tao Bao76def242017-11-21 09:25:31 -08003000 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3001 # its size. Quailty 9 almost triples the compression time but doesn't
3002 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003003 # zip | brotli(quality 6) | brotli(quality 9)
3004 # compressed_size: 942M | 869M (~8% reduced) | 854M
3005 # compression_time: 75s | 265s | 719s
3006 # decompression_time: 15s | 25s | 25s
3007
3008 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003009 brotli_cmd = ['brotli', '--quality=6',
3010 '--output={}.new.dat.br'.format(self.path),
3011 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003012 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003013 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003014
3015 new_data_name = '{}.new.dat.br'.format(self.partition)
3016 ZipWrite(output_zip,
3017 '{}.new.dat.br'.format(self.path),
3018 new_data_name,
3019 compress_type=zipfile.ZIP_STORED)
3020 else:
3021 new_data_name = '{}.new.dat'.format(self.partition)
3022 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3023
Dan Albert8e0178d2015-01-27 15:53:15 -08003024 ZipWrite(output_zip,
3025 '{}.patch.dat'.format(self.path),
3026 '{}.patch.dat'.format(self.partition),
3027 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003028
Tianjie Xu209db462016-05-24 17:34:52 -07003029 if self.partition == "system":
3030 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3031 else:
3032 code = ErrorCode.VENDOR_UPDATE_FAILURE
3033
Yifan Hong10c530d2018-12-27 17:34:18 -08003034 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003035 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003036 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003037 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003038 device=self.device, partition=self.partition,
3039 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003040 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003041
Kelvin Zhang0876c412020-06-23 15:06:58 -04003042 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003043 data = source.ReadRangeSet(ranges)
3044 ctx = sha1()
3045
3046 for p in data:
3047 ctx.update(p)
3048
3049 return ctx.hexdigest()
3050
Kelvin Zhang0876c412020-06-23 15:06:58 -04003051 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003052 """Return the hash value for all zero blocks."""
3053 zero_block = '\x00' * 4096
3054 ctx = sha1()
3055 for _ in range(num_blocks):
3056 ctx.update(zero_block)
3057
3058 return ctx.hexdigest()
3059
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003060
Tianjie Xu41976c72019-07-03 13:57:01 -07003061# Expose these two classes to support vendor-specific scripts
3062DataImage = images.DataImage
3063EmptyImage = images.EmptyImage
3064
Tao Bao76def242017-11-21 09:25:31 -08003065
Doug Zongker96a57e72010-09-26 14:57:41 -07003066# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003067PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003068 "ext4": "EMMC",
3069 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003070 "f2fs": "EMMC",
3071 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003072}
Doug Zongker96a57e72010-09-26 14:57:41 -07003073
Kelvin Zhang0876c412020-06-23 15:06:58 -04003074
Yifan Hongbdb32012020-05-07 12:38:53 -07003075def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3076 """
3077 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3078 backwards compatibility. It aborts if the fstab entry has slotselect option
3079 (unless check_no_slot is explicitly set to False).
3080 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003081 fstab = info["fstab"]
3082 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003083 if check_no_slot:
3084 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003085 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003086 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3087 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003088 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003089
3090
Yifan Hongbdb32012020-05-07 12:38:53 -07003091def GetTypeAndDeviceExpr(mount_point, info):
3092 """
3093 Return the filesystem of the partition, and an edify expression that evaluates
3094 to the device at runtime.
3095 """
3096 fstab = info["fstab"]
3097 if fstab:
3098 p = fstab[mount_point]
3099 device_expr = '"%s"' % fstab[mount_point].device
3100 if p.slotselect:
3101 device_expr = 'add_slot_suffix(%s)' % device_expr
3102 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003103 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003104
3105
3106def GetEntryForDevice(fstab, device):
3107 """
3108 Returns:
3109 The first entry in fstab whose device is the given value.
3110 """
3111 if not fstab:
3112 return None
3113 for mount_point in fstab:
3114 if fstab[mount_point].device == device:
3115 return fstab[mount_point]
3116 return None
3117
Kelvin Zhang0876c412020-06-23 15:06:58 -04003118
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003119def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003120 """Parses and converts a PEM-encoded certificate into DER-encoded.
3121
3122 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3123
3124 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003125 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003126 """
3127 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003128 save = False
3129 for line in data.split("\n"):
3130 if "--END CERTIFICATE--" in line:
3131 break
3132 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003133 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003134 if "--BEGIN CERTIFICATE--" in line:
3135 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003136 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003137 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003138
Tao Bao04e1f012018-02-04 12:13:35 -08003139
3140def ExtractPublicKey(cert):
3141 """Extracts the public key (PEM-encoded) from the given certificate file.
3142
3143 Args:
3144 cert: The certificate filename.
3145
3146 Returns:
3147 The public key string.
3148
3149 Raises:
3150 AssertionError: On non-zero return from 'openssl'.
3151 """
3152 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3153 # While openssl 1.1 writes the key into the given filename followed by '-out',
3154 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3155 # stdout instead.
3156 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3157 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3158 pubkey, stderrdata = proc.communicate()
3159 assert proc.returncode == 0, \
3160 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3161 return pubkey
3162
3163
Tao Bao1ac886e2019-06-26 11:58:22 -07003164def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003165 """Extracts the AVB public key from the given public or private key.
3166
3167 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003168 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003169 key: The input key file, which should be PEM-encoded public or private key.
3170
3171 Returns:
3172 The path to the extracted AVB public key file.
3173 """
3174 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3175 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003176 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003177 return output
3178
3179
Doug Zongker412c02f2014-02-13 10:58:24 -08003180def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3181 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003182 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003183
Tao Bao6d5d6232018-03-09 17:04:42 -08003184 Most of the space in the boot and recovery images is just the kernel, which is
3185 identical for the two, so the resulting patch should be efficient. Add it to
3186 the output zip, along with a shell script that is run from init.rc on first
3187 boot to actually do the patching and install the new recovery image.
3188
3189 Args:
3190 input_dir: The top-level input directory of the target-files.zip.
3191 output_sink: The callback function that writes the result.
3192 recovery_img: File object for the recovery image.
3193 boot_img: File objects for the boot image.
3194 info_dict: A dict returned by common.LoadInfoDict() on the input
3195 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003196 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003197 if info_dict is None:
3198 info_dict = OPTIONS.info_dict
3199
Tao Bao6d5d6232018-03-09 17:04:42 -08003200 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003201 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3202
3203 if board_uses_vendorimage:
3204 # In this case, the output sink is rooted at VENDOR
3205 recovery_img_path = "etc/recovery.img"
3206 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3207 sh_dir = "bin"
3208 else:
3209 # In this case the output sink is rooted at SYSTEM
3210 recovery_img_path = "vendor/etc/recovery.img"
3211 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3212 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003213
Tao Baof2cffbd2015-07-22 12:33:18 -07003214 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003215 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003216
3217 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003218 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003219 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003220 # With system-root-image, boot and recovery images will have mismatching
3221 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3222 # to handle such a case.
3223 if system_root_image:
3224 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003225 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003226 assert not os.path.exists(path)
3227 else:
3228 diff_program = ["imgdiff"]
3229 if os.path.exists(path):
3230 diff_program.append("-b")
3231 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003232 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003233 else:
3234 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003235
3236 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3237 _, _, patch = d.ComputePatch()
3238 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003239
Dan Albertebb19aa2015-03-27 19:11:53 -07003240 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003241 # The following GetTypeAndDevice()s need to use the path in the target
3242 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003243 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3244 check_no_slot=False)
3245 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3246 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003247 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003248 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003249
Tao Baof2cffbd2015-07-22 12:33:18 -07003250 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003251
3252 # Note that we use /vendor to refer to the recovery resources. This will
3253 # work for a separate vendor partition mounted at /vendor or a
3254 # /system/vendor subdirectory on the system partition, for which init will
3255 # create a symlink from /vendor to /system/vendor.
3256
3257 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003258if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3259 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003260 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003261 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3262 log -t recovery "Installing new recovery image: succeeded" || \\
3263 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003264else
3265 log -t recovery "Recovery image already installed"
3266fi
3267""" % {'type': recovery_type,
3268 'device': recovery_device,
3269 'sha1': recovery_img.sha1,
3270 'size': recovery_img.size}
3271 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003272 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003273if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3274 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003275 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003276 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3277 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3278 log -t recovery "Installing new recovery image: succeeded" || \\
3279 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003280else
3281 log -t recovery "Recovery image already installed"
3282fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003283""" % {'boot_size': boot_img.size,
3284 'boot_sha1': boot_img.sha1,
3285 'recovery_size': recovery_img.size,
3286 'recovery_sha1': recovery_img.sha1,
3287 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003288 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003289 'recovery_type': recovery_type,
3290 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003291 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003292
Bill Peckhame868aec2019-09-17 17:06:47 -07003293 # The install script location moved from /system/etc to /system/bin in the L
3294 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3295 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003296
Tao Bao32fcdab2018-10-12 10:30:39 -07003297 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003298
Tao Baoda30cfa2017-12-01 16:19:46 -08003299 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003300
3301
3302class DynamicPartitionUpdate(object):
3303 def __init__(self, src_group=None, tgt_group=None, progress=None,
3304 block_difference=None):
3305 self.src_group = src_group
3306 self.tgt_group = tgt_group
3307 self.progress = progress
3308 self.block_difference = block_difference
3309
3310 @property
3311 def src_size(self):
3312 if not self.block_difference:
3313 return 0
3314 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3315
3316 @property
3317 def tgt_size(self):
3318 if not self.block_difference:
3319 return 0
3320 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3321
3322 @staticmethod
3323 def _GetSparseImageSize(img):
3324 if not img:
3325 return 0
3326 return img.blocksize * img.total_blocks
3327
3328
3329class DynamicGroupUpdate(object):
3330 def __init__(self, src_size=None, tgt_size=None):
3331 # None: group does not exist. 0: no size limits.
3332 self.src_size = src_size
3333 self.tgt_size = tgt_size
3334
3335
3336class DynamicPartitionsDifference(object):
3337 def __init__(self, info_dict, block_diffs, progress_dict=None,
3338 source_info_dict=None):
3339 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003340 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003341
3342 self._remove_all_before_apply = False
3343 if source_info_dict is None:
3344 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003345 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003346
Tao Baof1113e92019-06-18 12:10:14 -07003347 block_diff_dict = collections.OrderedDict(
3348 [(e.partition, e) for e in block_diffs])
3349
Yifan Hong10c530d2018-12-27 17:34:18 -08003350 assert len(block_diff_dict) == len(block_diffs), \
3351 "Duplicated BlockDifference object for {}".format(
3352 [partition for partition, count in
3353 collections.Counter(e.partition for e in block_diffs).items()
3354 if count > 1])
3355
Yifan Hong79997e52019-01-23 16:56:19 -08003356 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003357
3358 for p, block_diff in block_diff_dict.items():
3359 self._partition_updates[p] = DynamicPartitionUpdate()
3360 self._partition_updates[p].block_difference = block_diff
3361
3362 for p, progress in progress_dict.items():
3363 if p in self._partition_updates:
3364 self._partition_updates[p].progress = progress
3365
3366 tgt_groups = shlex.split(info_dict.get(
3367 "super_partition_groups", "").strip())
3368 src_groups = shlex.split(source_info_dict.get(
3369 "super_partition_groups", "").strip())
3370
3371 for g in tgt_groups:
3372 for p in shlex.split(info_dict.get(
3373 "super_%s_partition_list" % g, "").strip()):
3374 assert p in self._partition_updates, \
3375 "{} is in target super_{}_partition_list but no BlockDifference " \
3376 "object is provided.".format(p, g)
3377 self._partition_updates[p].tgt_group = g
3378
3379 for g in src_groups:
3380 for p in shlex.split(source_info_dict.get(
3381 "super_%s_partition_list" % g, "").strip()):
3382 assert p in self._partition_updates, \
3383 "{} is in source super_{}_partition_list but no BlockDifference " \
3384 "object is provided.".format(p, g)
3385 self._partition_updates[p].src_group = g
3386
Yifan Hong45433e42019-01-18 13:55:25 -08003387 target_dynamic_partitions = set(shlex.split(info_dict.get(
3388 "dynamic_partition_list", "").strip()))
3389 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3390 if u.tgt_size)
3391 assert block_diffs_with_target == target_dynamic_partitions, \
3392 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3393 list(target_dynamic_partitions), list(block_diffs_with_target))
3394
3395 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3396 "dynamic_partition_list", "").strip()))
3397 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3398 if u.src_size)
3399 assert block_diffs_with_source == source_dynamic_partitions, \
3400 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3401 list(source_dynamic_partitions), list(block_diffs_with_source))
3402
Yifan Hong10c530d2018-12-27 17:34:18 -08003403 if self._partition_updates:
3404 logger.info("Updating dynamic partitions %s",
3405 self._partition_updates.keys())
3406
Yifan Hong79997e52019-01-23 16:56:19 -08003407 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003408
3409 for g in tgt_groups:
3410 self._group_updates[g] = DynamicGroupUpdate()
3411 self._group_updates[g].tgt_size = int(info_dict.get(
3412 "super_%s_group_size" % g, "0").strip())
3413
3414 for g in src_groups:
3415 if g not in self._group_updates:
3416 self._group_updates[g] = DynamicGroupUpdate()
3417 self._group_updates[g].src_size = int(source_info_dict.get(
3418 "super_%s_group_size" % g, "0").strip())
3419
3420 self._Compute()
3421
3422 def WriteScript(self, script, output_zip, write_verify_script=False):
3423 script.Comment('--- Start patching dynamic partitions ---')
3424 for p, u in self._partition_updates.items():
3425 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3426 script.Comment('Patch partition %s' % p)
3427 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3428 write_verify_script=False)
3429
3430 op_list_path = MakeTempFile()
3431 with open(op_list_path, 'w') as f:
3432 for line in self._op_list:
3433 f.write('{}\n'.format(line))
3434
3435 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3436
3437 script.Comment('Update dynamic partition metadata')
3438 script.AppendExtra('assert(update_dynamic_partitions('
3439 'package_extract_file("dynamic_partitions_op_list")));')
3440
3441 if write_verify_script:
3442 for p, u in self._partition_updates.items():
3443 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3444 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003445 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003446
3447 for p, u in self._partition_updates.items():
3448 if u.tgt_size and u.src_size <= u.tgt_size:
3449 script.Comment('Patch partition %s' % p)
3450 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3451 write_verify_script=write_verify_script)
3452 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003453 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003454
3455 script.Comment('--- End patching dynamic partitions ---')
3456
3457 def _Compute(self):
3458 self._op_list = list()
3459
3460 def append(line):
3461 self._op_list.append(line)
3462
3463 def comment(line):
3464 self._op_list.append("# %s" % line)
3465
3466 if self._remove_all_before_apply:
3467 comment('Remove all existing dynamic partitions and groups before '
3468 'applying full OTA')
3469 append('remove_all_groups')
3470
3471 for p, u in self._partition_updates.items():
3472 if u.src_group and not u.tgt_group:
3473 append('remove %s' % p)
3474
3475 for p, u in self._partition_updates.items():
3476 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3477 comment('Move partition %s from %s to default' % (p, u.src_group))
3478 append('move %s default' % p)
3479
3480 for p, u in self._partition_updates.items():
3481 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3482 comment('Shrink partition %s from %d to %d' %
3483 (p, u.src_size, u.tgt_size))
3484 append('resize %s %s' % (p, u.tgt_size))
3485
3486 for g, u in self._group_updates.items():
3487 if u.src_size is not None and u.tgt_size is None:
3488 append('remove_group %s' % g)
3489 if (u.src_size is not None and u.tgt_size is not None and
3490 u.src_size > u.tgt_size):
3491 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3492 append('resize_group %s %d' % (g, u.tgt_size))
3493
3494 for g, u in self._group_updates.items():
3495 if u.src_size is None and u.tgt_size is not None:
3496 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3497 append('add_group %s %d' % (g, u.tgt_size))
3498 if (u.src_size is not None and u.tgt_size is not None and
3499 u.src_size < u.tgt_size):
3500 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3501 append('resize_group %s %d' % (g, u.tgt_size))
3502
3503 for p, u in self._partition_updates.items():
3504 if u.tgt_group and not u.src_group:
3505 comment('Add partition %s to group %s' % (p, u.tgt_group))
3506 append('add %s %s' % (p, u.tgt_group))
3507
3508 for p, u in self._partition_updates.items():
3509 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003510 comment('Grow partition %s from %d to %d' %
3511 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003512 append('resize %s %d' % (p, u.tgt_size))
3513
3514 for p, u in self._partition_updates.items():
3515 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3516 comment('Move partition %s from default to %s' %
3517 (p, u.tgt_group))
3518 append('move %s %s' % (p, u.tgt_group))