blob: 0172add3a41b8f7bed42186bdbf74137b00ad132 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Kelvin Zhang0876c412020-06-23 15:06:58 -040020import datetime
Doug Zongker8ce7c252009-05-22 13:34:54 -070021import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070022import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070023import getopt
24import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010025import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070026import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070027import json
28import logging
29import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070030import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080031import platform
Doug Zongkereef39442009-04-02 12:14:19 -070032import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070033import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070034import shutil
35import subprocess
36import sys
37import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070038import threading
39import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070040import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080041from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070042
Tianjie Xu41976c72019-07-03 13:57:01 -070043import images
Tao Baoc765cca2018-01-31 17:32:40 -080044import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070045from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070046
Tao Bao32fcdab2018-10-12 10:30:39 -070047logger = logging.getLogger(__name__)
48
Tao Bao986ee862018-10-04 15:46:16 -070049
Dan Albert8b72aef2015-03-23 19:13:21 -070050class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070051
Dan Albert8b72aef2015-03-23 19:13:21 -070052 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070053 # Set up search path, in order to find framework/ and lib64/. At the time of
54 # running this function, user-supplied search path (`--path`) hasn't been
55 # available. So the value set here is the default, which might be overridden
56 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040057 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070058 if exec_path.endswith('.py'):
59 script_name = os.path.basename(exec_path)
60 # logger hasn't been initialized yet at this point. Use print to output
61 # warnings.
62 print(
63 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040064 'executable -- build and run `{}` directly.'.format(
65 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070066 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040067 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030068
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080070 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.extra_signapk_args = []
72 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080073 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080074 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.public_key_suffix = ".x509.pem"
76 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070077 # use otatools built boot_signer by default
78 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070079 self.boot_signer_args = []
80 self.verity_signer_path = None
81 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070082 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080083 self.aftl_server = None
84 self.aftl_key_path = None
85 self.aftl_manufacturer_key_path = None
86 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070087 self.verbose = False
88 self.tempfiles = []
89 self.device_specific = None
90 self.extras = {}
91 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070092 self.source_info_dict = None
93 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070094 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070095 # Stash size cannot exceed cache_size * threshold.
96 self.cache_size = None
97 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070098 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -070099 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700100
101
102OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700103
Tao Bao71197512018-10-11 14:08:45 -0700104# The block size that's used across the releasetools scripts.
105BLOCK_SIZE = 4096
106
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800107# Values for "certificate" in apkcerts that mean special things.
108SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
109
Tao Bao5cc0abb2019-03-21 10:18:05 -0700110# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
111# that system_other is not in the list because we don't want to include its
112# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900113AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700114 'system_ext', 'vendor', 'vendor_boot', 'vendor_dlkm',
115 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800116
Tao Bao08c190f2019-06-03 23:07:58 -0700117# Chained VBMeta partitions.
118AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
119
Tianjie Xu861f4132018-09-12 11:49:33 -0700120# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400121PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700122 'system',
123 'vendor',
124 'product',
125 'system_ext',
126 'odm',
127 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700128 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400129]
Tianjie Xu861f4132018-09-12 11:49:33 -0700130
131
Tianjie Xu209db462016-05-24 17:34:52 -0700132class ErrorCode(object):
133 """Define error_codes for failures that happen during the actual
134 update package installation.
135
136 Error codes 0-999 are reserved for failures before the package
137 installation (i.e. low battery, package verification failure).
138 Detailed code in 'bootable/recovery/error_code.h' """
139
140 SYSTEM_VERIFICATION_FAILURE = 1000
141 SYSTEM_UPDATE_FAILURE = 1001
142 SYSTEM_UNEXPECTED_CONTENTS = 1002
143 SYSTEM_NONZERO_CONTENTS = 1003
144 SYSTEM_RECOVER_FAILURE = 1004
145 VENDOR_VERIFICATION_FAILURE = 2000
146 VENDOR_UPDATE_FAILURE = 2001
147 VENDOR_UNEXPECTED_CONTENTS = 2002
148 VENDOR_NONZERO_CONTENTS = 2003
149 VENDOR_RECOVER_FAILURE = 2004
150 OEM_PROP_MISMATCH = 3000
151 FINGERPRINT_MISMATCH = 3001
152 THUMBPRINT_MISMATCH = 3002
153 OLDER_BUILD = 3003
154 DEVICE_MISMATCH = 3004
155 BAD_PATCH_FILE = 3005
156 INSUFFICIENT_CACHE_SPACE = 3006
157 TUNE_PARTITION_FAILURE = 3007
158 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800159
Tao Bao80921982018-03-21 21:02:19 -0700160
Dan Albert8b72aef2015-03-23 19:13:21 -0700161class ExternalError(RuntimeError):
162 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700163
164
Tao Bao32fcdab2018-10-12 10:30:39 -0700165def InitLogging():
166 DEFAULT_LOGGING_CONFIG = {
167 'version': 1,
168 'disable_existing_loggers': False,
169 'formatters': {
170 'standard': {
171 'format':
172 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
173 'datefmt': '%Y-%m-%d %H:%M:%S',
174 },
175 },
176 'handlers': {
177 'default': {
178 'class': 'logging.StreamHandler',
179 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700180 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700181 },
182 },
183 'loggers': {
184 '': {
185 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700186 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700187 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700188 }
189 }
190 }
191 env_config = os.getenv('LOGGING_CONFIG')
192 if env_config:
193 with open(env_config) as f:
194 config = json.load(f)
195 else:
196 config = DEFAULT_LOGGING_CONFIG
197
198 # Increase the logging level for verbose mode.
199 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700200 config = copy.deepcopy(config)
201 config['handlers']['default']['level'] = 'INFO'
202
203 if OPTIONS.logfile:
204 config = copy.deepcopy(config)
205 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400206 'class': 'logging.FileHandler',
207 'formatter': 'standard',
208 'level': 'INFO',
209 'mode': 'w',
210 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700211 }
212 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700213
214 logging.config.dictConfig(config)
215
216
Yifan Hong8e332ff2020-07-29 17:51:55 -0700217def SetHostToolLocation(tool_name, location):
218 OPTIONS.host_tools[tool_name] = location
219
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900220def FindHostToolPath(tool_name):
221 """Finds the path to the host tool.
222
223 Args:
224 tool_name: name of the tool to find
225 Returns:
226 path to the tool if found under either one of the host_tools map or under
227 the same directory as this binary is located at. If not found, tool_name
228 is returned.
229 """
230 if tool_name in OPTIONS.host_tools:
231 return OPTIONS.host_tools[tool_name]
232
233 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
234 tool_path = os.path.join(my_dir, tool_name)
235 if os.path.exists(tool_path):
236 return tool_path
237
238 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700239
Tao Bao39451582017-05-04 11:10:47 -0700240def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700241 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700242
Tao Bao73dd4f42018-10-04 16:25:33 -0700243 Args:
244 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700245 verbose: Whether the commands should be shown. Default to the global
246 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700247 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
248 stdin, etc. stdout and stderr will default to subprocess.PIPE and
249 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800250 universal_newlines will default to True, as most of the users in
251 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700252
253 Returns:
254 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700255 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700256 if 'stdout' not in kwargs and 'stderr' not in kwargs:
257 kwargs['stdout'] = subprocess.PIPE
258 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800259 if 'universal_newlines' not in kwargs:
260 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700261
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900262 if args:
263 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700264 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900265 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700266
Tao Bao32fcdab2018-10-12 10:30:39 -0700267 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400268 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700269 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700270 return subprocess.Popen(args, **kwargs)
271
272
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800273def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800274 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800275
276 Args:
277 args: The command represented as a list of strings.
278 verbose: Whether the commands should be shown. Default to the global
279 verbosity if unspecified.
280 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
281 stdin, etc. stdout and stderr will default to subprocess.PIPE and
282 subprocess.STDOUT respectively unless caller specifies any of them.
283
Bill Peckham889b0c62019-02-21 18:53:37 -0800284 Raises:
285 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800286 """
287 proc = Run(args, verbose=verbose, **kwargs)
288 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800289
290 if proc.returncode != 0:
291 raise ExternalError(
292 "Failed to run command '{}' (exit code {})".format(
293 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800294
295
Tao Bao986ee862018-10-04 15:46:16 -0700296def RunAndCheckOutput(args, verbose=None, **kwargs):
297 """Runs the given command and returns the output.
298
299 Args:
300 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700301 verbose: Whether the commands should be shown. Default to the global
302 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700303 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
304 stdin, etc. stdout and stderr will default to subprocess.PIPE and
305 subprocess.STDOUT respectively unless caller specifies any of them.
306
307 Returns:
308 The output string.
309
310 Raises:
311 ExternalError: On non-zero exit from the command.
312 """
Tao Bao986ee862018-10-04 15:46:16 -0700313 proc = Run(args, verbose=verbose, **kwargs)
314 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800315 if output is None:
316 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700317 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400318 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700319 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700320 if proc.returncode != 0:
321 raise ExternalError(
322 "Failed to run command '{}' (exit code {}):\n{}".format(
323 args, proc.returncode, output))
324 return output
325
326
Tao Baoc765cca2018-01-31 17:32:40 -0800327def RoundUpTo4K(value):
328 rounded_up = value + 4095
329 return rounded_up - (rounded_up % 4096)
330
331
Ying Wang7e6d4e42010-12-13 16:25:36 -0800332def CloseInheritedPipes():
333 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
334 before doing other work."""
335 if platform.system() != "Darwin":
336 return
337 for d in range(3, 1025):
338 try:
339 stat = os.fstat(d)
340 if stat is not None:
341 pipebit = stat[0] & 0x1000
342 if pipebit != 0:
343 os.close(d)
344 except OSError:
345 pass
346
347
Tao Bao1c320f82019-10-04 23:25:12 -0700348class BuildInfo(object):
349 """A class that holds the information for a given build.
350
351 This class wraps up the property querying for a given source or target build.
352 It abstracts away the logic of handling OEM-specific properties, and caches
353 the commonly used properties such as fingerprint.
354
355 There are two types of info dicts: a) build-time info dict, which is generated
356 at build time (i.e. included in a target_files zip); b) OEM info dict that is
357 specified at package generation time (via command line argument
358 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
359 having "oem_fingerprint_properties" in build-time info dict), all the queries
360 would be answered based on build-time info dict only. Otherwise if using
361 OEM-specific properties, some of them will be calculated from two info dicts.
362
363 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800364 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700365
366 Attributes:
367 info_dict: The build-time info dict.
368 is_ab: Whether it's a build that uses A/B OTA.
369 oem_dicts: A list of OEM dicts.
370 oem_props: A list of OEM properties that should be read from OEM dicts; None
371 if the build doesn't use any OEM-specific property.
372 fingerprint: The fingerprint of the build, which would be calculated based
373 on OEM properties if applicable.
374 device: The device name, which could come from OEM dicts if applicable.
375 """
376
377 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
378 "ro.product.manufacturer", "ro.product.model",
379 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700380 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
381 "product", "odm", "vendor", "system_ext", "system"]
382 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
383 "product", "product_services", "odm", "vendor", "system"]
384 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700385
Tao Bao3ed35d32019-10-07 20:48:48 -0700386 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700387 """Initializes a BuildInfo instance with the given dicts.
388
389 Note that it only wraps up the given dicts, without making copies.
390
391 Arguments:
392 info_dict: The build-time info dict.
393 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
394 that it always uses the first dict to calculate the fingerprint or the
395 device name. The rest would be used for asserting OEM properties only
396 (e.g. one package can be installed on one of these devices).
397
398 Raises:
399 ValueError: On invalid inputs.
400 """
401 self.info_dict = info_dict
402 self.oem_dicts = oem_dicts
403
404 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700405
Hongguang Chend7c160f2020-05-03 21:24:26 -0700406 # Skip _oem_props if oem_dicts is None to use BuildInfo in
407 # sign_target_files_apks
408 if self.oem_dicts:
409 self._oem_props = info_dict.get("oem_fingerprint_properties")
410 else:
411 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700412
Daniel Normand5fe8622020-01-08 17:01:11 -0800413 def check_fingerprint(fingerprint):
414 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
415 raise ValueError(
416 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
417 "3.2.2. Build Parameters.".format(fingerprint))
418
Daniel Normand5fe8622020-01-08 17:01:11 -0800419 self._partition_fingerprints = {}
420 for partition in PARTITIONS_WITH_CARE_MAP:
421 try:
422 fingerprint = self.CalculatePartitionFingerprint(partition)
423 check_fingerprint(fingerprint)
424 self._partition_fingerprints[partition] = fingerprint
425 except ExternalError:
426 continue
427 if "system" in self._partition_fingerprints:
428 # system_other is not included in PARTITIONS_WITH_CARE_MAP, but does
429 # need a fingerprint when creating the image.
430 self._partition_fingerprints[
431 "system_other"] = self._partition_fingerprints["system"]
432
Tao Bao1c320f82019-10-04 23:25:12 -0700433 # These two should be computed only after setting self._oem_props.
434 self._device = self.GetOemProperty("ro.product.device")
435 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800436 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700437
438 @property
439 def is_ab(self):
440 return self._is_ab
441
442 @property
443 def device(self):
444 return self._device
445
446 @property
447 def fingerprint(self):
448 return self._fingerprint
449
450 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700451 def oem_props(self):
452 return self._oem_props
453
454 def __getitem__(self, key):
455 return self.info_dict[key]
456
457 def __setitem__(self, key, value):
458 self.info_dict[key] = value
459
460 def get(self, key, default=None):
461 return self.info_dict.get(key, default)
462
463 def items(self):
464 return self.info_dict.items()
465
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000466 def _GetRawBuildProp(self, prop, partition):
467 prop_file = '{}.build.prop'.format(
468 partition) if partition else 'build.prop'
469 partition_props = self.info_dict.get(prop_file)
470 if not partition_props:
471 return None
472 return partition_props.GetProp(prop)
473
Daniel Normand5fe8622020-01-08 17:01:11 -0800474 def GetPartitionBuildProp(self, prop, partition):
475 """Returns the inquired build property for the provided partition."""
476 # If provided a partition for this property, only look within that
477 # partition's build.prop.
478 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
479 prop = prop.replace("ro.product", "ro.product.{}".format(partition))
480 else:
481 prop = prop.replace("ro.", "ro.{}.".format(partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000482
483 prop_val = self._GetRawBuildProp(prop, partition)
484 if prop_val is not None:
485 return prop_val
486 raise ExternalError("couldn't find %s in %s.build.prop" %
487 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800488
Tao Bao1c320f82019-10-04 23:25:12 -0700489 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800490 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700491 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
492 return self._ResolveRoProductBuildProp(prop)
493
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000494 prop_val = self._GetRawBuildProp(prop, None)
495 if prop_val is not None:
496 return prop_val
497
498 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700499
500 def _ResolveRoProductBuildProp(self, prop):
501 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000502 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700503 if prop_val:
504 return prop_val
505
Steven Laver8e2086e2020-04-27 16:26:31 -0700506 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000507 source_order_val = self._GetRawBuildProp(
508 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700509 if source_order_val:
510 source_order = source_order_val.split(",")
511 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700512 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700513
514 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700515 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700516 raise ExternalError(
517 "Invalid ro.product.property_source_order '{}'".format(source_order))
518
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000519 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700520 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000521 "ro.product", "ro.product.{}".format(source_partition), 1)
522 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700523 if prop_val:
524 return prop_val
525
526 raise ExternalError("couldn't resolve {}".format(prop))
527
Steven Laver8e2086e2020-04-27 16:26:31 -0700528 def _GetRoProductPropsDefaultSourceOrder(self):
529 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
530 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000531 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700532 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000533 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700534 if android_version == "10":
535 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
536 # NOTE: float() conversion of android_version will have rounding error.
537 # We are checking for "9" or less, and using "< 10" is well outside of
538 # possible floating point rounding.
539 try:
540 android_version_val = float(android_version)
541 except ValueError:
542 android_version_val = 0
543 if android_version_val < 10:
544 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
545 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
546
Tianjieb37c5be2020-10-15 21:27:10 -0700547 def _GetPlatformVersion(self):
548 version_sdk = self.GetBuildProp("ro.build.version.sdk")
549 # init code switches to version_release_or_codename (see b/158483506). After
550 # API finalization, release_or_codename will be the same as release. This
551 # is the best effort to support pre-S dev stage builds.
552 if int(version_sdk) >= 30:
553 try:
554 return self.GetBuildProp("ro.build.version.release_or_codename")
555 except ExternalError:
556 logger.warning('Failed to find ro.build.version.release_or_codename')
557
558 return self.GetBuildProp("ro.build.version.release")
559
560 def _GetPartitionPlatformVersion(self, partition):
561 try:
562 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
563 partition)
564 except ExternalError:
565 return self.GetPartitionBuildProp("ro.build.version.release",
566 partition)
567
Tao Bao1c320f82019-10-04 23:25:12 -0700568 def GetOemProperty(self, key):
569 if self.oem_props is not None and key in self.oem_props:
570 return self.oem_dicts[0][key]
571 return self.GetBuildProp(key)
572
Daniel Normand5fe8622020-01-08 17:01:11 -0800573 def GetPartitionFingerprint(self, partition):
574 return self._partition_fingerprints.get(partition, None)
575
576 def CalculatePartitionFingerprint(self, partition):
577 try:
578 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
579 except ExternalError:
580 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
581 self.GetPartitionBuildProp("ro.product.brand", partition),
582 self.GetPartitionBuildProp("ro.product.name", partition),
583 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700584 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800585 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400586 self.GetPartitionBuildProp(
587 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800588 self.GetPartitionBuildProp("ro.build.type", partition),
589 self.GetPartitionBuildProp("ro.build.tags", partition))
590
Tao Bao1c320f82019-10-04 23:25:12 -0700591 def CalculateFingerprint(self):
592 if self.oem_props is None:
593 try:
594 return self.GetBuildProp("ro.build.fingerprint")
595 except ExternalError:
596 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
597 self.GetBuildProp("ro.product.brand"),
598 self.GetBuildProp("ro.product.name"),
599 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700600 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700601 self.GetBuildProp("ro.build.id"),
602 self.GetBuildProp("ro.build.version.incremental"),
603 self.GetBuildProp("ro.build.type"),
604 self.GetBuildProp("ro.build.tags"))
605 return "%s/%s/%s:%s" % (
606 self.GetOemProperty("ro.product.brand"),
607 self.GetOemProperty("ro.product.name"),
608 self.GetOemProperty("ro.product.device"),
609 self.GetBuildProp("ro.build.thumbprint"))
610
611 def WriteMountOemScript(self, script):
612 assert self.oem_props is not None
613 recovery_mount_options = self.info_dict.get("recovery_mount_options")
614 script.Mount("/oem", recovery_mount_options)
615
616 def WriteDeviceAssertions(self, script, oem_no_mount):
617 # Read the property directly if not using OEM properties.
618 if not self.oem_props:
619 script.AssertDevice(self.device)
620 return
621
622 # Otherwise assert OEM properties.
623 if not self.oem_dicts:
624 raise ExternalError(
625 "No OEM file provided to answer expected assertions")
626
627 for prop in self.oem_props.split():
628 values = []
629 for oem_dict in self.oem_dicts:
630 if prop in oem_dict:
631 values.append(oem_dict[prop])
632 if not values:
633 raise ExternalError(
634 "The OEM file is missing the property %s" % (prop,))
635 script.AssertOemProperty(prop, values, oem_no_mount)
636
637
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000638def ReadFromInputFile(input_file, fn):
639 """Reads the contents of fn from input zipfile or directory."""
640 if isinstance(input_file, zipfile.ZipFile):
641 return input_file.read(fn).decode()
642 else:
643 path = os.path.join(input_file, *fn.split("/"))
644 try:
645 with open(path) as f:
646 return f.read()
647 except IOError as e:
648 if e.errno == errno.ENOENT:
649 raise KeyError(fn)
650
651
Tao Bao410ad8b2018-08-24 12:08:38 -0700652def LoadInfoDict(input_file, repacking=False):
653 """Loads the key/value pairs from the given input target_files.
654
Tianjiea85bdf02020-07-29 11:56:19 -0700655 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700656 checks and returns the parsed key/value pairs for to the given build. It's
657 usually called early when working on input target_files files, e.g. when
658 generating OTAs, or signing builds. Note that the function may be called
659 against an old target_files file (i.e. from past dessert releases). So the
660 property parsing needs to be backward compatible.
661
662 In a `META/misc_info.txt`, a few properties are stored as links to the files
663 in the PRODUCT_OUT directory. It works fine with the build system. However,
664 they are no longer available when (re)generating images from target_files zip.
665 When `repacking` is True, redirect these properties to the actual files in the
666 unzipped directory.
667
668 Args:
669 input_file: The input target_files file, which could be an open
670 zipfile.ZipFile instance, or a str for the dir that contains the files
671 unzipped from a target_files file.
672 repacking: Whether it's trying repack an target_files file after loading the
673 info dict (default: False). If so, it will rewrite a few loaded
674 properties (e.g. selinux_fc, root_dir) to point to the actual files in
675 target_files file. When doing repacking, `input_file` must be a dir.
676
677 Returns:
678 A dict that contains the parsed key/value pairs.
679
680 Raises:
681 AssertionError: On invalid input arguments.
682 ValueError: On malformed input values.
683 """
684 if repacking:
685 assert isinstance(input_file, str), \
686 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700687
Doug Zongkerc9253822014-02-04 12:17:58 -0800688 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000689 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800690
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700691 try:
Michael Runge6e836112014-04-15 17:40:21 -0700692 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700693 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700694 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700695
Tao Bao410ad8b2018-08-24 12:08:38 -0700696 if "recovery_api_version" not in d:
697 raise ValueError("Failed to find 'recovery_api_version'")
698 if "fstab_version" not in d:
699 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800700
Tao Bao410ad8b2018-08-24 12:08:38 -0700701 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700702 # "selinux_fc" properties should point to the file_contexts files
703 # (file_contexts.bin) under META/.
704 for key in d:
705 if key.endswith("selinux_fc"):
706 fc_basename = os.path.basename(d[key])
707 fc_config = os.path.join(input_file, "META", fc_basename)
708 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700709
Daniel Norman72c626f2019-05-13 15:58:14 -0700710 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700711
Tom Cherryd14b8952018-08-09 14:26:00 -0700712 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700713 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700714 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700715 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700716
David Anderson0ec64ac2019-12-06 12:21:18 -0800717 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700718 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700719 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800720 key_name = part_name + "_base_fs_file"
721 if key_name not in d:
722 continue
723 basename = os.path.basename(d[key_name])
724 base_fs_file = os.path.join(input_file, "META", basename)
725 if os.path.exists(base_fs_file):
726 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700727 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700728 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800729 "Failed to find %s base fs file: %s", part_name, base_fs_file)
730 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700731
Doug Zongker37974732010-09-16 17:44:38 -0700732 def makeint(key):
733 if key in d:
734 d[key] = int(d[key], 0)
735
736 makeint("recovery_api_version")
737 makeint("blocksize")
738 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700739 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700740 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700741 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700742 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800743 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700744
Steve Muckle903a1ca2020-05-07 17:32:10 -0700745 boot_images = "boot.img"
746 if "boot_images" in d:
747 boot_images = d["boot_images"]
748 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400749 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700750
Tao Bao765668f2019-10-04 22:03:00 -0700751 # Load recovery fstab if applicable.
752 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800753
Tianjie Xu861f4132018-09-12 11:49:33 -0700754 # Tries to load the build props for all partitions with care_map, including
755 # system and vendor.
756 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800757 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000758 d[partition_prop] = PartitionBuildProps.FromInputFile(
759 input_file, partition)
Tianjie Xu861f4132018-09-12 11:49:33 -0700760 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800761
Tao Bao3ed35d32019-10-07 20:48:48 -0700762 # Set up the salt (based on fingerprint) that will be used when adding AVB
763 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800764 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700765 build_info = BuildInfo(d)
Daniel Normand5fe8622020-01-08 17:01:11 -0800766 for partition in PARTITIONS_WITH_CARE_MAP:
767 fingerprint = build_info.GetPartitionFingerprint(partition)
768 if fingerprint:
Kelvin Zhang0876c412020-06-23 15:06:58 -0400769 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400770 try:
771 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
772 except KeyError:
773 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700774 return d
775
Tao Baod1de6f32017-03-01 16:38:48 -0800776
Kelvin Zhang39aea442020-08-17 11:04:25 -0400777
Daniel Norman4cc9df62019-07-18 10:11:07 -0700778def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900779 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700780 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900781
Daniel Norman4cc9df62019-07-18 10:11:07 -0700782
783def LoadDictionaryFromFile(file_path):
784 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900785 return LoadDictionaryFromLines(lines)
786
787
Michael Runge6e836112014-04-15 17:40:21 -0700788def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700789 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700790 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700791 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700792 if not line or line.startswith("#"):
793 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700794 if "=" in line:
795 name, value = line.split("=", 1)
796 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700797 return d
798
Tao Baod1de6f32017-03-01 16:38:48 -0800799
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000800class PartitionBuildProps(object):
801 """The class holds the build prop of a particular partition.
802
803 This class loads the build.prop and holds the build properties for a given
804 partition. It also partially recognizes the 'import' statement in the
805 build.prop; and calculates alternative values of some specific build
806 properties during runtime.
807
808 Attributes:
809 input_file: a zipped target-file or an unzipped target-file directory.
810 partition: name of the partition.
811 props_allow_override: a list of build properties to search for the
812 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000813 build_props: a dict of build properties for the given partition.
814 prop_overrides: a set of props that are overridden by import.
815 placeholder_values: A dict of runtime variables' values to replace the
816 placeholders in the build.prop file. We expect exactly one value for
817 each of the variables.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000818 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400819
Tianjie Xu9afb2212020-05-10 21:48:15 +0000820 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000821 self.input_file = input_file
822 self.partition = name
823 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000824 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000825 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000826 self.prop_overrides = set()
827 self.placeholder_values = {}
828 if placeholder_values:
829 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000830
831 @staticmethod
832 def FromDictionary(name, build_props):
833 """Constructs an instance from a build prop dictionary."""
834
835 props = PartitionBuildProps("unknown", name)
836 props.build_props = build_props.copy()
837 return props
838
839 @staticmethod
Tianjie Xu9afb2212020-05-10 21:48:15 +0000840 def FromInputFile(input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000841 """Loads the build.prop file and builds the attributes."""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000842 data = ''
843 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
844 '{}/build.prop'.format(name.upper())]:
845 try:
846 data = ReadFromInputFile(input_file, prop_file)
847 break
848 except KeyError:
849 logger.warning('Failed to read %s', prop_file)
850
Tianjie Xu9afb2212020-05-10 21:48:15 +0000851 props = PartitionBuildProps(input_file, name, placeholder_values)
852 props._LoadBuildProp(data)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000853 return props
854
Yifan Hong125d0b62020-09-24 17:07:03 -0700855 @staticmethod
856 def FromBuildPropFile(name, build_prop_file):
857 """Constructs an instance from a build prop file."""
858
859 props = PartitionBuildProps("unknown", name)
860 with open(build_prop_file) as f:
861 props._LoadBuildProp(f.read())
862 return props
863
Tianjie Xu9afb2212020-05-10 21:48:15 +0000864 def _LoadBuildProp(self, data):
865 for line in data.split('\n'):
866 line = line.strip()
867 if not line or line.startswith("#"):
868 continue
869 if line.startswith("import"):
870 overrides = self._ImportParser(line)
871 duplicates = self.prop_overrides.intersection(overrides.keys())
872 if duplicates:
873 raise ValueError('prop {} is overridden multiple times'.format(
874 ','.join(duplicates)))
875 self.prop_overrides = self.prop_overrides.union(overrides.keys())
876 self.build_props.update(overrides)
877 elif "=" in line:
878 name, value = line.split("=", 1)
879 if name in self.prop_overrides:
880 raise ValueError('prop {} is set again after overridden by import '
881 'statement'.format(name))
882 self.build_props[name] = value
883
884 def _ImportParser(self, line):
885 """Parses the build prop in a given import statement."""
886
887 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400888 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000889 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700890
891 if len(tokens) == 3:
892 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
893 return {}
894
Tianjie Xu9afb2212020-05-10 21:48:15 +0000895 import_path = tokens[1]
896 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
897 raise ValueError('Unrecognized import path {}'.format(line))
898
899 # We only recognize a subset of import statement that the init process
900 # supports. And we can loose the restriction based on how the dynamic
901 # fingerprint is used in practice. The placeholder format should be
902 # ${placeholder}, and its value should be provided by the caller through
903 # the placeholder_values.
904 for prop, value in self.placeholder_values.items():
905 prop_place_holder = '${{{}}}'.format(prop)
906 if prop_place_holder in import_path:
907 import_path = import_path.replace(prop_place_holder, value)
908 if '$' in import_path:
909 logger.info('Unresolved place holder in import path %s', import_path)
910 return {}
911
912 import_path = import_path.replace('/{}'.format(self.partition),
913 self.partition.upper())
914 logger.info('Parsing build props override from %s', import_path)
915
916 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
917 d = LoadDictionaryFromLines(lines)
918 return {key: val for key, val in d.items()
919 if key in self.props_allow_override}
920
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000921 def GetProp(self, prop):
922 return self.build_props.get(prop)
923
924
Tianjie Xucfa86222016-03-07 16:31:19 -0800925def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
926 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700927 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700928 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700929 self.mount_point = mount_point
930 self.fs_type = fs_type
931 self.device = device
932 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700933 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700934 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700935
936 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800937 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700938 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700939 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700940 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700941
Tao Baod1de6f32017-03-01 16:38:48 -0800942 assert fstab_version == 2
943
944 d = {}
945 for line in data.split("\n"):
946 line = line.strip()
947 if not line or line.startswith("#"):
948 continue
949
950 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
951 pieces = line.split()
952 if len(pieces) != 5:
953 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
954
955 # Ignore entries that are managed by vold.
956 options = pieces[4]
957 if "voldmanaged=" in options:
958 continue
959
960 # It's a good line, parse it.
961 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -0700962 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -0800963 options = options.split(",")
964 for i in options:
965 if i.startswith("length="):
966 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -0700967 elif i == "slotselect":
968 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -0800969 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800970 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700971 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800972
Tao Baod1de6f32017-03-01 16:38:48 -0800973 mount_flags = pieces[3]
974 # Honor the SELinux context if present.
975 context = None
976 for i in mount_flags.split(","):
977 if i.startswith("context="):
978 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800979
Tao Baod1de6f32017-03-01 16:38:48 -0800980 mount_point = pieces[1]
981 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -0700982 device=pieces[0], length=length, context=context,
983 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800984
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700985 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700986 # system. Other areas assume system is always at "/system" so point /system
987 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700988 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800989 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700990 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700991 return d
992
993
Tao Bao765668f2019-10-04 22:03:00 -0700994def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
995 """Finds the path to recovery fstab and loads its contents."""
996 # recovery fstab is only meaningful when installing an update via recovery
997 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -0700998 if info_dict.get('ab_update') == 'true' and \
999 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001000 return None
1001
1002 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1003 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1004 # cases, since it may load the info_dict from an old build (e.g. when
1005 # generating incremental OTAs from that build).
1006 system_root_image = info_dict.get('system_root_image') == 'true'
1007 if info_dict.get('no_recovery') != 'true':
1008 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1009 if isinstance(input_file, zipfile.ZipFile):
1010 if recovery_fstab_path not in input_file.namelist():
1011 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1012 else:
1013 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1014 if not os.path.exists(path):
1015 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1016 return LoadRecoveryFSTab(
1017 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1018 system_root_image)
1019
1020 if info_dict.get('recovery_as_boot') == 'true':
1021 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1022 if isinstance(input_file, zipfile.ZipFile):
1023 if recovery_fstab_path not in input_file.namelist():
1024 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1025 else:
1026 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1027 if not os.path.exists(path):
1028 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1029 return LoadRecoveryFSTab(
1030 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1031 system_root_image)
1032
1033 return None
1034
1035
Doug Zongker37974732010-09-16 17:44:38 -07001036def DumpInfoDict(d):
1037 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001038 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001039
Dan Albert8b72aef2015-03-23 19:13:21 -07001040
Daniel Norman55417142019-11-25 16:04:36 -08001041def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001042 """Merges dynamic partition info variables.
1043
1044 Args:
1045 framework_dict: The dictionary of dynamic partition info variables from the
1046 partial framework target files.
1047 vendor_dict: The dictionary of dynamic partition info variables from the
1048 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001049
1050 Returns:
1051 The merged dynamic partition info dictionary.
1052 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001053
1054 def uniq_concat(a, b):
1055 combined = set(a.split(" "))
1056 combined.update(set(b.split(" ")))
1057 combined = [item.strip() for item in combined if item.strip()]
1058 return " ".join(sorted(combined))
1059
1060 if (framework_dict.get("use_dynamic_partitions") !=
1061 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
1062 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1063
1064 merged_dict = {"use_dynamic_partitions": "true"}
1065
1066 merged_dict["dynamic_partition_list"] = uniq_concat(
1067 framework_dict.get("dynamic_partition_list", ""),
1068 vendor_dict.get("dynamic_partition_list", ""))
1069
1070 # Super block devices are defined by the vendor dict.
1071 if "super_block_devices" in vendor_dict:
1072 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1073 for block_device in merged_dict["super_block_devices"].split(" "):
1074 key = "super_%s_device_size" % block_device
1075 if key not in vendor_dict:
1076 raise ValueError("Vendor dict does not contain required key %s." % key)
1077 merged_dict[key] = vendor_dict[key]
1078
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001079 # Partition groups and group sizes are defined by the vendor dict because
1080 # these values may vary for each board that uses a shared system image.
1081 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001082 for partition_group in merged_dict["super_partition_groups"].split(" "):
1083 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001084 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001085 if key not in vendor_dict:
1086 raise ValueError("Vendor dict does not contain required key %s." % key)
1087 merged_dict[key] = vendor_dict[key]
1088
1089 # Set the partition group's partition list using a concatenation of the
1090 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001091 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001092 merged_dict[key] = uniq_concat(
1093 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301094
Daniel Normanb0c75912020-09-24 14:30:21 -07001095 # Various other flags should be copied from the vendor dict, if defined.
1096 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1097 "super_metadata_device", "super_partition_error_limit",
1098 "super_partition_size"):
1099 if key in vendor_dict.keys():
1100 merged_dict[key] = vendor_dict[key]
1101
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001102 return merged_dict
1103
1104
Daniel Norman21c34f72020-11-11 17:25:50 -08001105def PartitionMapFromTargetFiles(target_files_dir):
1106 """Builds a map from partition -> path within an extracted target files directory."""
1107 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1108 possible_subdirs = {
1109 "system": ["SYSTEM"],
1110 "vendor": ["VENDOR", "SYSTEM/vendor"],
1111 "product": ["PRODUCT", "SYSTEM/product"],
1112 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1113 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1114 "vendor_dlkm": [
1115 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1116 ],
1117 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1118 }
1119 partition_map = {}
1120 for partition, subdirs in possible_subdirs.items():
1121 for subdir in subdirs:
1122 if os.path.exists(os.path.join(target_files_dir, subdir)):
1123 partition_map[partition] = subdir
1124 break
1125 return partition_map
1126
1127
Daniel Normand3351562020-10-29 12:33:11 -07001128def SharedUidPartitionViolations(uid_dict, partition_groups):
1129 """Checks for APK sharedUserIds that cross partition group boundaries.
1130
1131 This uses a single or merged build's shareduid_violation_modules.json
1132 output file, as generated by find_shareduid_violation.py or
1133 core/tasks/find-shareduid-violation.mk.
1134
1135 An error is defined as a sharedUserId that is found in a set of partitions
1136 that span more than one partition group.
1137
1138 Args:
1139 uid_dict: A dictionary created by using the standard json module to read a
1140 complete shareduid_violation_modules.json file.
1141 partition_groups: A list of groups, where each group is a list of
1142 partitions.
1143
1144 Returns:
1145 A list of error messages.
1146 """
1147 errors = []
1148 for uid, partitions in uid_dict.items():
1149 found_in_groups = [
1150 group for group in partition_groups
1151 if set(partitions.keys()) & set(group)
1152 ]
1153 if len(found_in_groups) > 1:
1154 errors.append(
1155 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1156 % (uid, ",".join(sorted(partitions.keys()))))
1157 return errors
1158
1159
Daniel Norman21c34f72020-11-11 17:25:50 -08001160def RunHostInitVerifier(product_out, partition_map):
1161 """Runs host_init_verifier on the init rc files within partitions.
1162
1163 host_init_verifier searches the etc/init path within each partition.
1164
1165 Args:
1166 product_out: PRODUCT_OUT directory, containing partition directories.
1167 partition_map: A map of partition name -> relative path within product_out.
1168 """
1169 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1170 cmd = ["host_init_verifier"]
1171 for partition, path in partition_map.items():
1172 if partition not in allowed_partitions:
1173 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1174 partition)
1175 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1176 # Add --property-contexts if the file exists on the partition.
1177 property_contexts = "%s_property_contexts" % (
1178 "plat" if partition == "system" else partition)
1179 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1180 property_contexts)
1181 if os.path.exists(property_contexts_path):
1182 cmd.append("--property-contexts=%s" % property_contexts_path)
1183 # Add the passwd file if the file exists on the partition.
1184 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1185 if os.path.exists(passwd_path):
1186 cmd.extend(["-p", passwd_path])
1187 return RunAndCheckOutput(cmd)
1188
1189
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001190def AppendAVBSigningArgs(cmd, partition):
1191 """Append signing arguments for avbtool."""
1192 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1193 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001194 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1195 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1196 if os.path.exists(new_key_path):
1197 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001198 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1199 if key_path and algorithm:
1200 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001201 avb_salt = OPTIONS.info_dict.get("avb_salt")
1202 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001203 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001204 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001205
1206
Tao Bao765668f2019-10-04 22:03:00 -07001207def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001208 """Returns the VBMeta arguments for partition.
1209
1210 It sets up the VBMeta argument by including the partition descriptor from the
1211 given 'image', or by configuring the partition as a chained partition.
1212
1213 Args:
1214 partition: The name of the partition (e.g. "system").
1215 image: The path to the partition image.
1216 info_dict: A dict returned by common.LoadInfoDict(). Will use
1217 OPTIONS.info_dict if None has been given.
1218
1219 Returns:
1220 A list of VBMeta arguments.
1221 """
1222 if info_dict is None:
1223 info_dict = OPTIONS.info_dict
1224
1225 # Check if chain partition is used.
1226 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001227 if not key_path:
1228 return ["--include_descriptors_from_image", image]
1229
1230 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1231 # into vbmeta.img. The recovery image will be configured on an independent
1232 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1233 # See details at
1234 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001235 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001236 return []
1237
1238 # Otherwise chain the partition into vbmeta.
1239 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1240 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001241
1242
Tao Bao02a08592018-07-22 12:40:45 -07001243def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1244 """Constructs and returns the arg to build or verify a chained partition.
1245
1246 Args:
1247 partition: The partition name.
1248 info_dict: The info dict to look up the key info and rollback index
1249 location.
1250 key: The key to be used for building or verifying the partition. Defaults to
1251 the key listed in info_dict.
1252
1253 Returns:
1254 A string of form "partition:rollback_index_location:key" that can be used to
1255 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001256 """
1257 if key is None:
1258 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001259 if key and not os.path.exists(key) and OPTIONS.search_path:
1260 new_key_path = os.path.join(OPTIONS.search_path, key)
1261 if os.path.exists(new_key_path):
1262 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001263 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001264 rollback_index_location = info_dict[
1265 "avb_" + partition + "_rollback_index_location"]
1266 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1267
1268
Tianjie20dd8f22020-04-19 15:51:16 -07001269def ConstructAftlMakeImageCommands(output_image):
1270 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001271
1272 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001273 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001274 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1275 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1276 'No AFTL manufacturer key provided.'
1277
1278 vbmeta_image = MakeTempFile()
1279 os.rename(output_image, vbmeta_image)
1280 build_info = BuildInfo(OPTIONS.info_dict)
1281 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001282 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001283 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001284 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001285 "--vbmeta_image_path", vbmeta_image,
1286 "--output", output_image,
1287 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001288 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001289 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1290 "--algorithm", "SHA256_RSA4096",
1291 "--padding", "4096"]
1292 if OPTIONS.aftl_signer_helper:
1293 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001294 return aftl_cmd
1295
1296
1297def AddAftlInclusionProof(output_image):
1298 """Appends the aftl inclusion proof to the vbmeta image."""
1299
1300 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001301 RunAndCheckOutput(aftl_cmd)
1302
1303 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1304 output_image, '--transparency_log_pub_keys',
1305 OPTIONS.aftl_key_path]
1306 RunAndCheckOutput(verify_cmd)
1307
1308
Daniel Norman276f0622019-07-26 14:13:51 -07001309def BuildVBMeta(image_path, partitions, name, needed_partitions):
1310 """Creates a VBMeta image.
1311
1312 It generates the requested VBMeta image. The requested image could be for
1313 top-level or chained VBMeta image, which is determined based on the name.
1314
1315 Args:
1316 image_path: The output path for the new VBMeta image.
1317 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001318 values. Only valid partition names are accepted, as partitions listed
1319 in common.AVB_PARTITIONS and custom partitions listed in
1320 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001321 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1322 needed_partitions: Partitions whose descriptors should be included into the
1323 generated VBMeta image.
1324
1325 Raises:
1326 AssertionError: On invalid input args.
1327 """
1328 avbtool = OPTIONS.info_dict["avb_avbtool"]
1329 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1330 AppendAVBSigningArgs(cmd, name)
1331
Hongguang Chenf23364d2020-04-27 18:36:36 -07001332 custom_partitions = OPTIONS.info_dict.get(
1333 "avb_custom_images_partition_list", "").strip().split()
1334
Daniel Norman276f0622019-07-26 14:13:51 -07001335 for partition, path in partitions.items():
1336 if partition not in needed_partitions:
1337 continue
1338 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001339 partition in AVB_VBMETA_PARTITIONS or
1340 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001341 'Unknown partition: {}'.format(partition)
1342 assert os.path.exists(path), \
1343 'Failed to find {} for {}'.format(path, partition)
1344 cmd.extend(GetAvbPartitionArg(partition, path))
1345
1346 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1347 if args and args.strip():
1348 split_args = shlex.split(args)
1349 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001350 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001351 # as a path relative to source tree, which may not be available at the
1352 # same location when running this script (we have the input target_files
1353 # zip only). For such cases, we additionally scan other locations (e.g.
1354 # IMAGES/, RADIO/, etc) before bailing out.
1355 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001356 chained_image = split_args[index + 1]
1357 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001358 continue
1359 found = False
1360 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1361 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001362 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001363 if os.path.exists(alt_path):
1364 split_args[index + 1] = alt_path
1365 found = True
1366 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001367 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001368 cmd.extend(split_args)
1369
1370 RunAndCheckOutput(cmd)
1371
Tianjie Xueaed60c2020-03-12 00:33:28 -07001372 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001373 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001374 AddAftlInclusionProof(image_path)
1375
Daniel Norman276f0622019-07-26 14:13:51 -07001376
J. Avila98cd4cc2020-06-10 20:09:10 +00001377def _MakeRamdisk(sourcedir, fs_config_file=None, lz4_ramdisks=False):
Steve Mucklee1b10862019-07-10 10:49:37 -07001378 ramdisk_img = tempfile.NamedTemporaryFile()
1379
1380 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1381 cmd = ["mkbootfs", "-f", fs_config_file,
1382 os.path.join(sourcedir, "RAMDISK")]
1383 else:
1384 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1385 p1 = Run(cmd, stdout=subprocess.PIPE)
J. Avila98cd4cc2020-06-10 20:09:10 +00001386 if lz4_ramdisks:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001387 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001388 stdout=ramdisk_img.file.fileno())
1389 else:
1390 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
Steve Mucklee1b10862019-07-10 10:49:37 -07001391
1392 p2.wait()
1393 p1.wait()
1394 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001395 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001396
1397 return ramdisk_img
1398
1399
Steve Muckle9793cf62020-04-08 18:27:00 -07001400def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001401 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001402 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001403
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001404 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001405 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1406 we are building a two-step special image (i.e. building a recovery image to
1407 be loaded into /boot in two-step OTAs).
1408
1409 Return the image data, or None if sourcedir does not appear to contains files
1410 for building the requested image.
1411 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001412
Yifan Hong63c5ca12020-10-08 11:54:02 -07001413 if info_dict is None:
1414 info_dict = OPTIONS.info_dict
1415
Steve Muckle9793cf62020-04-08 18:27:00 -07001416 # "boot" or "recovery", without extension.
1417 partition_name = os.path.basename(sourcedir).lower()
1418
Yifan Hong63c5ca12020-10-08 11:54:02 -07001419 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001420 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001421 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1422 logger.info("Excluded kernel binary from recovery image.")
1423 else:
1424 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001425 else:
1426 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001427 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001428 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001429 return None
1430
1431 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001432 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001433
Doug Zongkereef39442009-04-02 12:14:19 -07001434 img = tempfile.NamedTemporaryFile()
1435
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001436 if has_ramdisk:
J. Avila98cd4cc2020-06-10 20:09:10 +00001437 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1438 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file, lz4_ramdisks=use_lz4)
Doug Zongkereef39442009-04-02 12:14:19 -07001439
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001440 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1441 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1442
Yifan Hong63c5ca12020-10-08 11:54:02 -07001443 cmd = [mkbootimg]
1444 if kernel:
1445 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001446
Benoit Fradina45a8682014-07-14 21:00:43 +02001447 fn = os.path.join(sourcedir, "second")
1448 if os.access(fn, os.F_OK):
1449 cmd.append("--second")
1450 cmd.append(fn)
1451
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001452 fn = os.path.join(sourcedir, "dtb")
1453 if os.access(fn, os.F_OK):
1454 cmd.append("--dtb")
1455 cmd.append(fn)
1456
Doug Zongker171f1cd2009-06-15 22:36:37 -07001457 fn = os.path.join(sourcedir, "cmdline")
1458 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001459 cmd.append("--cmdline")
1460 cmd.append(open(fn).read().rstrip("\n"))
1461
1462 fn = os.path.join(sourcedir, "base")
1463 if os.access(fn, os.F_OK):
1464 cmd.append("--base")
1465 cmd.append(open(fn).read().rstrip("\n"))
1466
Ying Wang4de6b5b2010-08-25 14:29:34 -07001467 fn = os.path.join(sourcedir, "pagesize")
1468 if os.access(fn, os.F_OK):
1469 cmd.append("--pagesize")
1470 cmd.append(open(fn).read().rstrip("\n"))
1471
Steve Mucklef84668e2020-03-16 19:13:46 -07001472 if partition_name == "recovery":
1473 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301474 if not args:
1475 # Fall back to "mkbootimg_args" for recovery image
1476 # in case "recovery_mkbootimg_args" is not set.
1477 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001478 else:
1479 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001480 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001481 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001482
Tao Bao76def242017-11-21 09:25:31 -08001483 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001484 if args and args.strip():
1485 cmd.extend(shlex.split(args))
1486
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001487 if has_ramdisk:
1488 cmd.extend(["--ramdisk", ramdisk_img.name])
1489
Tao Baod95e9fd2015-03-29 23:07:41 -07001490 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001491 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001492 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001493 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001494 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001495 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001496
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001497 if partition_name == "recovery":
1498 if info_dict.get("include_recovery_dtbo") == "true":
1499 fn = os.path.join(sourcedir, "recovery_dtbo")
1500 cmd.extend(["--recovery_dtbo", fn])
1501 if info_dict.get("include_recovery_acpio") == "true":
1502 fn = os.path.join(sourcedir, "recovery_acpio")
1503 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001504
Tao Bao986ee862018-10-04 15:46:16 -07001505 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001506
Tao Bao76def242017-11-21 09:25:31 -08001507 if (info_dict.get("boot_signer") == "true" and
1508 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001509 # Hard-code the path as "/boot" for two-step special recovery image (which
1510 # will be loaded into /boot during the two-step OTA).
1511 if two_step_image:
1512 path = "/boot"
1513 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001514 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001515 cmd = [OPTIONS.boot_signer_path]
1516 cmd.extend(OPTIONS.boot_signer_args)
1517 cmd.extend([path, img.name,
1518 info_dict["verity_key"] + ".pk8",
1519 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001520 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001521
Tao Baod95e9fd2015-03-29 23:07:41 -07001522 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001523 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001524 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001525 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001526 # We have switched from the prebuilt futility binary to using the tool
1527 # (futility-host) built from the source. Override the setting in the old
1528 # TF.zip.
1529 futility = info_dict["futility"]
1530 if futility.startswith("prebuilts/"):
1531 futility = "futility-host"
1532 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001533 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001534 info_dict["vboot_key"] + ".vbprivk",
1535 info_dict["vboot_subkey"] + ".vbprivk",
1536 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001537 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001538 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001539
Tao Baof3282b42015-04-01 11:21:55 -07001540 # Clean up the temp files.
1541 img_unsigned.close()
1542 img_keyblock.close()
1543
David Zeuthen8fecb282017-12-01 16:24:01 -05001544 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001545 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001546 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001547 if partition_name == "recovery":
1548 part_size = info_dict["recovery_size"]
1549 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001550 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001551 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001552 "--partition_size", str(part_size), "--partition_name",
1553 partition_name]
1554 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001555 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001556 if args and args.strip():
1557 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001558 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001559
1560 img.seek(os.SEEK_SET, 0)
1561 data = img.read()
1562
1563 if has_ramdisk:
1564 ramdisk_img.close()
1565 img.close()
1566
1567 return data
1568
1569
Doug Zongkerd5131602012-08-02 14:46:42 -07001570def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001571 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001572 """Return a File object with the desired bootable image.
1573
1574 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1575 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1576 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001577
Doug Zongker55d93282011-01-25 17:03:34 -08001578 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1579 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001580 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001581 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001582
1583 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1584 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001585 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001586 return File.FromLocalFile(name, prebuilt_path)
1587
Tao Bao32fcdab2018-10-12 10:30:39 -07001588 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001589
1590 if info_dict is None:
1591 info_dict = OPTIONS.info_dict
1592
1593 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001594 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1595 # for recovery.
1596 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1597 prebuilt_name != "boot.img" or
1598 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001599
Doug Zongker6f1d0312014-08-22 08:07:12 -07001600 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001601 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001602 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001603 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001604 if data:
1605 return File(name, data)
1606 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001607
Doug Zongkereef39442009-04-02 12:14:19 -07001608
Steve Mucklee1b10862019-07-10 10:49:37 -07001609def _BuildVendorBootImage(sourcedir, info_dict=None):
1610 """Build a vendor boot image from the specified sourcedir.
1611
1612 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1613 turn them into a vendor boot image.
1614
1615 Return the image data, or None if sourcedir does not appear to contains files
1616 for building the requested image.
1617 """
1618
1619 if info_dict is None:
1620 info_dict = OPTIONS.info_dict
1621
1622 img = tempfile.NamedTemporaryFile()
1623
J. Avila98cd4cc2020-06-10 20:09:10 +00001624 use_lz4 = info_dict.get("lz4_ramdisks") == 'true'
1625 ramdisk_img = _MakeRamdisk(sourcedir, lz4_ramdisks=use_lz4)
Steve Mucklee1b10862019-07-10 10:49:37 -07001626
1627 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1628 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1629
1630 cmd = [mkbootimg]
1631
1632 fn = os.path.join(sourcedir, "dtb")
1633 if os.access(fn, os.F_OK):
1634 cmd.append("--dtb")
1635 cmd.append(fn)
1636
1637 fn = os.path.join(sourcedir, "vendor_cmdline")
1638 if os.access(fn, os.F_OK):
1639 cmd.append("--vendor_cmdline")
1640 cmd.append(open(fn).read().rstrip("\n"))
1641
1642 fn = os.path.join(sourcedir, "base")
1643 if os.access(fn, os.F_OK):
1644 cmd.append("--base")
1645 cmd.append(open(fn).read().rstrip("\n"))
1646
1647 fn = os.path.join(sourcedir, "pagesize")
1648 if os.access(fn, os.F_OK):
1649 cmd.append("--pagesize")
1650 cmd.append(open(fn).read().rstrip("\n"))
1651
1652 args = info_dict.get("mkbootimg_args")
1653 if args and args.strip():
1654 cmd.extend(shlex.split(args))
1655
1656 args = info_dict.get("mkbootimg_version_args")
1657 if args and args.strip():
1658 cmd.extend(shlex.split(args))
1659
1660 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1661 cmd.extend(["--vendor_boot", img.name])
1662
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001663 ramdisk_fragment_imgs = []
1664 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1665 if os.access(fn, os.F_OK):
1666 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1667 for ramdisk_fragment in ramdisk_fragments:
1668 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "mkbootimg_args")
1669 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
1670 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "prebuilt_ramdisk")
1671 # Use prebuilt image if found, else create ramdisk from supplied files.
1672 if os.access(fn, os.F_OK):
1673 ramdisk_fragment_pathname = fn
1674 else:
1675 ramdisk_fragment_root = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
1676 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root, lz4_ramdisks=use_lz4)
1677 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1678 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1679 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1680
Steve Mucklee1b10862019-07-10 10:49:37 -07001681 RunAndCheckOutput(cmd)
1682
1683 # AVB: if enabled, calculate and add hash.
1684 if info_dict.get("avb_enable") == "true":
1685 avbtool = info_dict["avb_avbtool"]
1686 part_size = info_dict["vendor_boot_size"]
1687 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001688 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001689 AppendAVBSigningArgs(cmd, "vendor_boot")
1690 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1691 if args and args.strip():
1692 cmd.extend(shlex.split(args))
1693 RunAndCheckOutput(cmd)
1694
1695 img.seek(os.SEEK_SET, 0)
1696 data = img.read()
1697
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001698 for f in ramdisk_fragment_imgs:
1699 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001700 ramdisk_img.close()
1701 img.close()
1702
1703 return data
1704
1705
1706def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1707 info_dict=None):
1708 """Return a File object with the desired vendor boot image.
1709
1710 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1711 the source files in 'unpack_dir'/'tree_subdir'."""
1712
1713 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1714 if os.path.exists(prebuilt_path):
1715 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1716 return File.FromLocalFile(name, prebuilt_path)
1717
1718 logger.info("building image from target_files %s...", tree_subdir)
1719
1720 if info_dict is None:
1721 info_dict = OPTIONS.info_dict
1722
Kelvin Zhang0876c412020-06-23 15:06:58 -04001723 data = _BuildVendorBootImage(
1724 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001725 if data:
1726 return File(name, data)
1727 return None
1728
1729
Narayan Kamatha07bf042017-08-14 14:49:21 +01001730def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001731 """Gunzips the given gzip compressed file to a given output file."""
1732 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001733 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001734 shutil.copyfileobj(in_file, out_file)
1735
1736
Tao Bao0ff15de2019-03-20 11:26:06 -07001737def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001738 """Unzips the archive to the given directory.
1739
1740 Args:
1741 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001742 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001743 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1744 archvie. Non-matching patterns will be filtered out. If there's no match
1745 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001746 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001747 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001748 if patterns is not None:
1749 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001750 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001751 names = input_zip.namelist()
1752 filtered = [
1753 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1754
1755 # There isn't any matching files. Don't unzip anything.
1756 if not filtered:
1757 return
1758 cmd.extend(filtered)
1759
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001760 RunAndCheckOutput(cmd)
1761
1762
Doug Zongker75f17362009-12-08 13:46:44 -08001763def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001764 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001765
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001766 Args:
1767 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1768 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1769
1770 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1771 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001772
Tao Bao1c830bf2017-12-25 10:43:47 -08001773 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001774 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001775 """
Doug Zongkereef39442009-04-02 12:14:19 -07001776
Tao Bao1c830bf2017-12-25 10:43:47 -08001777 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001778 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1779 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001780 UnzipToDir(m.group(1), tmp, pattern)
1781 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001782 filename = m.group(1)
1783 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001784 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001785
Tao Baodba59ee2018-01-09 13:21:02 -08001786 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001787
1788
Yifan Hong8a66a712019-04-04 15:37:57 -07001789def GetUserImage(which, tmpdir, input_zip,
1790 info_dict=None,
1791 allow_shared_blocks=None,
1792 hashtree_info_generator=None,
1793 reset_file_map=False):
1794 """Returns an Image object suitable for passing to BlockImageDiff.
1795
1796 This function loads the specified image from the given path. If the specified
1797 image is sparse, it also performs additional processing for OTA purpose. For
1798 example, it always adds block 0 to clobbered blocks list. It also detects
1799 files that cannot be reconstructed from the block list, for whom we should
1800 avoid applying imgdiff.
1801
1802 Args:
1803 which: The partition name.
1804 tmpdir: The directory that contains the prebuilt image and block map file.
1805 input_zip: The target-files ZIP archive.
1806 info_dict: The dict to be looked up for relevant info.
1807 allow_shared_blocks: If image is sparse, whether having shared blocks is
1808 allowed. If none, it is looked up from info_dict.
1809 hashtree_info_generator: If present and image is sparse, generates the
1810 hashtree_info for this sparse image.
1811 reset_file_map: If true and image is sparse, reset file map before returning
1812 the image.
1813 Returns:
1814 A Image object. If it is a sparse image and reset_file_map is False, the
1815 image will have file_map info loaded.
1816 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001817 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001818 info_dict = LoadInfoDict(input_zip)
1819
1820 is_sparse = info_dict.get("extfs_sparse_flag")
1821
1822 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1823 # shared blocks (i.e. some blocks will show up in multiple files' block
1824 # list). We can only allocate such shared blocks to the first "owner", and
1825 # disable imgdiff for all later occurrences.
1826 if allow_shared_blocks is None:
1827 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1828
1829 if is_sparse:
1830 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1831 hashtree_info_generator)
1832 if reset_file_map:
1833 img.ResetFileMap()
1834 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001835 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001836
1837
1838def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1839 """Returns a Image object suitable for passing to BlockImageDiff.
1840
1841 This function loads the specified non-sparse image from the given path.
1842
1843 Args:
1844 which: The partition name.
1845 tmpdir: The directory that contains the prebuilt image and block map file.
1846 Returns:
1847 A Image object.
1848 """
1849 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1850 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1851
1852 # The image and map files must have been created prior to calling
1853 # ota_from_target_files.py (since LMP).
1854 assert os.path.exists(path) and os.path.exists(mappath)
1855
Tianjie Xu41976c72019-07-03 13:57:01 -07001856 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1857
Yifan Hong8a66a712019-04-04 15:37:57 -07001858
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001859def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1860 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001861 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1862
1863 This function loads the specified sparse image from the given path, and
1864 performs additional processing for OTA purpose. For example, it always adds
1865 block 0 to clobbered blocks list. It also detects files that cannot be
1866 reconstructed from the block list, for whom we should avoid applying imgdiff.
1867
1868 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001869 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001870 tmpdir: The directory that contains the prebuilt image and block map file.
1871 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001872 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001873 hashtree_info_generator: If present, generates the hashtree_info for this
1874 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001875 Returns:
1876 A SparseImage object, with file_map info loaded.
1877 """
Tao Baoc765cca2018-01-31 17:32:40 -08001878 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1879 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1880
1881 # The image and map files must have been created prior to calling
1882 # ota_from_target_files.py (since LMP).
1883 assert os.path.exists(path) and os.path.exists(mappath)
1884
1885 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1886 # it to clobbered_blocks so that it will be written to the target
1887 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1888 clobbered_blocks = "0"
1889
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001890 image = sparse_img.SparseImage(
1891 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1892 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001893
1894 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1895 # if they contain all zeros. We can't reconstruct such a file from its block
1896 # list. Tag such entries accordingly. (Bug: 65213616)
1897 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001898 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001899 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001900 continue
1901
Tom Cherryd14b8952018-08-09 14:26:00 -07001902 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1903 # filename listed in system.map may contain an additional leading slash
1904 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1905 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001906 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001907
Tom Cherryd14b8952018-08-09 14:26:00 -07001908 # Special handling another case, where files not under /system
1909 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001910 if which == 'system' and not arcname.startswith('SYSTEM'):
1911 arcname = 'ROOT/' + arcname
1912
1913 assert arcname in input_zip.namelist(), \
1914 "Failed to find the ZIP entry for {}".format(entry)
1915
Tao Baoc765cca2018-01-31 17:32:40 -08001916 info = input_zip.getinfo(arcname)
1917 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001918
1919 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001920 # image, check the original block list to determine its completeness. Note
1921 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001922 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001923 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001924
Tao Baoc765cca2018-01-31 17:32:40 -08001925 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1926 ranges.extra['incomplete'] = True
1927
1928 return image
1929
1930
Doug Zongkereef39442009-04-02 12:14:19 -07001931def GetKeyPasswords(keylist):
1932 """Given a list of keys, prompt the user to enter passwords for
1933 those which require them. Return a {key: password} dict. password
1934 will be None if the key has no password."""
1935
Doug Zongker8ce7c252009-05-22 13:34:54 -07001936 no_passwords = []
1937 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001938 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001939 devnull = open("/dev/null", "w+b")
1940 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001941 # We don't need a password for things that aren't really keys.
1942 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001943 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001944 continue
1945
T.R. Fullhart37e10522013-03-18 10:31:26 -07001946 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001947 "-inform", "DER", "-nocrypt"],
1948 stdin=devnull.fileno(),
1949 stdout=devnull.fileno(),
1950 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001951 p.communicate()
1952 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001953 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001954 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001955 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001956 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1957 "-inform", "DER", "-passin", "pass:"],
1958 stdin=devnull.fileno(),
1959 stdout=devnull.fileno(),
1960 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001961 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001962 if p.returncode == 0:
1963 # Encrypted key with empty string as password.
1964 key_passwords[k] = ''
1965 elif stderr.startswith('Error decrypting key'):
1966 # Definitely encrypted key.
1967 # It would have said "Error reading key" if it didn't parse correctly.
1968 need_passwords.append(k)
1969 else:
1970 # Potentially, a type of key that openssl doesn't understand.
1971 # We'll let the routines in signapk.jar handle it.
1972 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001973 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001974
T.R. Fullhart37e10522013-03-18 10:31:26 -07001975 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001976 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001977 return key_passwords
1978
1979
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001980def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001981 """Gets the minSdkVersion declared in the APK.
1982
changho.shin0f125362019-07-08 10:59:00 +09001983 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001984 This can be both a decimal number (API Level) or a codename.
1985
1986 Args:
1987 apk_name: The APK filename.
1988
1989 Returns:
1990 The parsed SDK version string.
1991
1992 Raises:
1993 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001994 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001995 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001996 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001997 stderr=subprocess.PIPE)
1998 stdoutdata, stderrdata = proc.communicate()
1999 if proc.returncode != 0:
2000 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002001 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002002 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002003
Tao Baof47bf0f2018-03-21 23:28:51 -07002004 for line in stdoutdata.split("\n"):
2005 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002006 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2007 if m:
2008 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002009 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002010
2011
2012def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002013 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002014
Tao Baof47bf0f2018-03-21 23:28:51 -07002015 If minSdkVersion is set to a codename, it is translated to a number using the
2016 provided map.
2017
2018 Args:
2019 apk_name: The APK filename.
2020
2021 Returns:
2022 The parsed SDK version number.
2023
2024 Raises:
2025 ExternalError: On failing to get the min SDK version number.
2026 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002027 version = GetMinSdkVersion(apk_name)
2028 try:
2029 return int(version)
2030 except ValueError:
2031 # Not a decimal number. Codename?
2032 if version in codename_to_api_level_map:
2033 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002034 raise ExternalError(
2035 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2036 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002037
2038
2039def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002040 codename_to_api_level_map=None, whole_file=False,
2041 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002042 """Sign the input_name zip/jar/apk, producing output_name. Use the
2043 given key and password (the latter may be None if the key does not
2044 have a password.
2045
Doug Zongker951495f2009-08-14 12:44:19 -07002046 If whole_file is true, use the "-w" option to SignApk to embed a
2047 signature that covers the whole file in the archive comment of the
2048 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002049
2050 min_api_level is the API Level (int) of the oldest platform this file may end
2051 up on. If not specified for an APK, the API Level is obtained by interpreting
2052 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2053
2054 codename_to_api_level_map is needed to translate the codename which may be
2055 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002056
2057 Caller may optionally specify extra args to be passed to SignApk, which
2058 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002059 """
Tao Bao76def242017-11-21 09:25:31 -08002060 if codename_to_api_level_map is None:
2061 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002062 if extra_signapk_args is None:
2063 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002064
Alex Klyubin9667b182015-12-10 13:38:50 -08002065 java_library_path = os.path.join(
2066 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2067
Tao Baoe95540e2016-11-08 12:08:53 -08002068 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2069 ["-Djava.library.path=" + java_library_path,
2070 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002071 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002072 if whole_file:
2073 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002074
2075 min_sdk_version = min_api_level
2076 if min_sdk_version is None:
2077 if not whole_file:
2078 min_sdk_version = GetMinSdkVersionInt(
2079 input_name, codename_to_api_level_map)
2080 if min_sdk_version is not None:
2081 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2082
T.R. Fullhart37e10522013-03-18 10:31:26 -07002083 cmd.extend([key + OPTIONS.public_key_suffix,
2084 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002085 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002086
Tao Bao73dd4f42018-10-04 16:25:33 -07002087 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002088 if password is not None:
2089 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002090 stdoutdata, _ = proc.communicate(password)
2091 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002092 raise ExternalError(
2093 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002094 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002095
Doug Zongkereef39442009-04-02 12:14:19 -07002096
Doug Zongker37974732010-09-16 17:44:38 -07002097def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002098 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002099
Tao Bao9dd909e2017-11-14 11:27:32 -08002100 For non-AVB images, raise exception if the data is too big. Print a warning
2101 if the data is nearing the maximum size.
2102
2103 For AVB images, the actual image size should be identical to the limit.
2104
2105 Args:
2106 data: A string that contains all the data for the partition.
2107 target: The partition name. The ".img" suffix is optional.
2108 info_dict: The dict to be looked up for relevant info.
2109 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002110 if target.endswith(".img"):
2111 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002112 mount_point = "/" + target
2113
Ying Wangf8824af2014-06-03 14:07:27 -07002114 fs_type = None
2115 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002116 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002117 if mount_point == "/userdata":
2118 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002119 p = info_dict["fstab"][mount_point]
2120 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002121 device = p.device
2122 if "/" in device:
2123 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002124 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002125 if not fs_type or not limit:
2126 return
Doug Zongkereef39442009-04-02 12:14:19 -07002127
Andrew Boie0f9aec82012-02-14 09:32:52 -08002128 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002129 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2130 # path.
2131 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2132 if size != limit:
2133 raise ExternalError(
2134 "Mismatching image size for %s: expected %d actual %d" % (
2135 target, limit, size))
2136 else:
2137 pct = float(size) * 100.0 / limit
2138 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2139 if pct >= 99.0:
2140 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002141
2142 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002143 logger.warning("\n WARNING: %s\n", msg)
2144 else:
2145 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002146
2147
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002148def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002149 """Parses the APK certs info from a given target-files zip.
2150
2151 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2152 tuple with the following elements: (1) a dictionary that maps packages to
2153 certs (based on the "certificate" and "private_key" attributes in the file;
2154 (2) a string representing the extension of compressed APKs in the target files
2155 (e.g ".gz", ".bro").
2156
2157 Args:
2158 tf_zip: The input target_files ZipFile (already open).
2159
2160 Returns:
2161 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2162 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2163 no compressed APKs.
2164 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002165 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002166 compressed_extension = None
2167
Tao Bao0f990332017-09-08 19:02:54 -07002168 # META/apkcerts.txt contains the info for _all_ the packages known at build
2169 # time. Filter out the ones that are not installed.
2170 installed_files = set()
2171 for name in tf_zip.namelist():
2172 basename = os.path.basename(name)
2173 if basename:
2174 installed_files.add(basename)
2175
Tao Baoda30cfa2017-12-01 16:19:46 -08002176 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002177 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002178 if not line:
2179 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002180 m = re.match(
2181 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002182 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2183 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002184 line)
2185 if not m:
2186 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002187
Tao Bao818ddf52018-01-05 11:17:34 -08002188 matches = m.groupdict()
2189 cert = matches["CERT"]
2190 privkey = matches["PRIVKEY"]
2191 name = matches["NAME"]
2192 this_compressed_extension = matches["COMPRESSED"]
2193
2194 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2195 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2196 if cert in SPECIAL_CERT_STRINGS and not privkey:
2197 certmap[name] = cert
2198 elif (cert.endswith(OPTIONS.public_key_suffix) and
2199 privkey.endswith(OPTIONS.private_key_suffix) and
2200 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2201 certmap[name] = cert[:-public_key_suffix_len]
2202 else:
2203 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2204
2205 if not this_compressed_extension:
2206 continue
2207
2208 # Only count the installed files.
2209 filename = name + '.' + this_compressed_extension
2210 if filename not in installed_files:
2211 continue
2212
2213 # Make sure that all the values in the compression map have the same
2214 # extension. We don't support multiple compression methods in the same
2215 # system image.
2216 if compressed_extension:
2217 if this_compressed_extension != compressed_extension:
2218 raise ValueError(
2219 "Multiple compressed extensions: {} vs {}".format(
2220 compressed_extension, this_compressed_extension))
2221 else:
2222 compressed_extension = this_compressed_extension
2223
2224 return (certmap,
2225 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002226
2227
Doug Zongkereef39442009-04-02 12:14:19 -07002228COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002229Global options
2230
2231 -p (--path) <dir>
2232 Prepend <dir>/bin to the list of places to search for binaries run by this
2233 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002234
Doug Zongker05d3dea2009-06-22 11:32:31 -07002235 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002236 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002237
Tao Bao30df8b42018-04-23 15:32:53 -07002238 -x (--extra) <key=value>
2239 Add a key/value pair to the 'extras' dict, which device-specific extension
2240 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002241
Doug Zongkereef39442009-04-02 12:14:19 -07002242 -v (--verbose)
2243 Show command lines being executed.
2244
2245 -h (--help)
2246 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002247
2248 --logfile <file>
2249 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002250"""
2251
Kelvin Zhang0876c412020-06-23 15:06:58 -04002252
Doug Zongkereef39442009-04-02 12:14:19 -07002253def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002254 print(docstring.rstrip("\n"))
2255 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002256
2257
2258def ParseOptions(argv,
2259 docstring,
2260 extra_opts="", extra_long_opts=(),
2261 extra_option_handler=None):
2262 """Parse the options in argv and return any arguments that aren't
2263 flags. docstring is the calling module's docstring, to be displayed
2264 for errors and -h. extra_opts and extra_long_opts are for flags
2265 defined by the caller, which are processed by passing them to
2266 extra_option_handler."""
2267
2268 try:
2269 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002270 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002271 ["help", "verbose", "path=", "signapk_path=",
2272 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002273 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002274 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2275 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002276 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2277 "aftl_key_path=", "aftl_manufacturer_key_path=",
2278 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002279 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002280 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002281 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002282 sys.exit(2)
2283
Doug Zongkereef39442009-04-02 12:14:19 -07002284 for o, a in opts:
2285 if o in ("-h", "--help"):
2286 Usage(docstring)
2287 sys.exit()
2288 elif o in ("-v", "--verbose"):
2289 OPTIONS.verbose = True
2290 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002291 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002292 elif o in ("--signapk_path",):
2293 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002294 elif o in ("--signapk_shared_library_path",):
2295 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002296 elif o in ("--extra_signapk_args",):
2297 OPTIONS.extra_signapk_args = shlex.split(a)
2298 elif o in ("--java_path",):
2299 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002300 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002301 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002302 elif o in ("--android_jar_path",):
2303 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002304 elif o in ("--public_key_suffix",):
2305 OPTIONS.public_key_suffix = a
2306 elif o in ("--private_key_suffix",):
2307 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002308 elif o in ("--boot_signer_path",):
2309 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002310 elif o in ("--boot_signer_args",):
2311 OPTIONS.boot_signer_args = shlex.split(a)
2312 elif o in ("--verity_signer_path",):
2313 OPTIONS.verity_signer_path = a
2314 elif o in ("--verity_signer_args",):
2315 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002316 elif o in ("--aftl_tool_path",):
2317 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002318 elif o in ("--aftl_server",):
2319 OPTIONS.aftl_server = a
2320 elif o in ("--aftl_key_path",):
2321 OPTIONS.aftl_key_path = a
2322 elif o in ("--aftl_manufacturer_key_path",):
2323 OPTIONS.aftl_manufacturer_key_path = a
2324 elif o in ("--aftl_signer_helper",):
2325 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002326 elif o in ("-s", "--device_specific"):
2327 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002328 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002329 key, value = a.split("=", 1)
2330 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002331 elif o in ("--logfile",):
2332 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002333 else:
2334 if extra_option_handler is None or not extra_option_handler(o, a):
2335 assert False, "unknown option \"%s\"" % (o,)
2336
Doug Zongker85448772014-09-09 14:59:20 -07002337 if OPTIONS.search_path:
2338 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2339 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002340
2341 return args
2342
2343
Tao Bao4c851b12016-09-19 13:54:38 -07002344def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002345 """Make a temp file and add it to the list of things to be deleted
2346 when Cleanup() is called. Return the filename."""
2347 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2348 os.close(fd)
2349 OPTIONS.tempfiles.append(fn)
2350 return fn
2351
2352
Tao Bao1c830bf2017-12-25 10:43:47 -08002353def MakeTempDir(prefix='tmp', suffix=''):
2354 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2355
2356 Returns:
2357 The absolute pathname of the new directory.
2358 """
2359 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2360 OPTIONS.tempfiles.append(dir_name)
2361 return dir_name
2362
2363
Doug Zongkereef39442009-04-02 12:14:19 -07002364def Cleanup():
2365 for i in OPTIONS.tempfiles:
2366 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002367 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002368 else:
2369 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002370 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002371
2372
2373class PasswordManager(object):
2374 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002375 self.editor = os.getenv("EDITOR")
2376 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002377
2378 def GetPasswords(self, items):
2379 """Get passwords corresponding to each string in 'items',
2380 returning a dict. (The dict may have keys in addition to the
2381 values in 'items'.)
2382
2383 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2384 user edit that file to add more needed passwords. If no editor is
2385 available, or $ANDROID_PW_FILE isn't define, prompts the user
2386 interactively in the ordinary way.
2387 """
2388
2389 current = self.ReadFile()
2390
2391 first = True
2392 while True:
2393 missing = []
2394 for i in items:
2395 if i not in current or not current[i]:
2396 missing.append(i)
2397 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002398 if not missing:
2399 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002400
2401 for i in missing:
2402 current[i] = ""
2403
2404 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002405 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002406 if sys.version_info[0] >= 3:
2407 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002408 answer = raw_input("try to edit again? [y]> ").strip()
2409 if answer and answer[0] not in 'yY':
2410 raise RuntimeError("key passwords unavailable")
2411 first = False
2412
2413 current = self.UpdateAndReadFile(current)
2414
Kelvin Zhang0876c412020-06-23 15:06:58 -04002415 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002416 """Prompt the user to enter a value (password) for each key in
2417 'current' whose value is fales. Returns a new dict with all the
2418 values.
2419 """
2420 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002421 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002422 if v:
2423 result[k] = v
2424 else:
2425 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002426 result[k] = getpass.getpass(
2427 "Enter password for %s key> " % k).strip()
2428 if result[k]:
2429 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002430 return result
2431
2432 def UpdateAndReadFile(self, current):
2433 if not self.editor or not self.pwfile:
2434 return self.PromptResult(current)
2435
2436 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002437 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002438 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2439 f.write("# (Additional spaces are harmless.)\n\n")
2440
2441 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002442 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002443 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002444 f.write("[[[ %s ]]] %s\n" % (v, k))
2445 if not v and first_line is None:
2446 # position cursor on first line with no password.
2447 first_line = i + 4
2448 f.close()
2449
Tao Bao986ee862018-10-04 15:46:16 -07002450 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002451
2452 return self.ReadFile()
2453
2454 def ReadFile(self):
2455 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002456 if self.pwfile is None:
2457 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002458 try:
2459 f = open(self.pwfile, "r")
2460 for line in f:
2461 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002462 if not line or line[0] == '#':
2463 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002464 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2465 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002466 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002467 else:
2468 result[m.group(2)] = m.group(1)
2469 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002470 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002471 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002472 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002473 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002474
2475
Dan Albert8e0178d2015-01-27 15:53:15 -08002476def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2477 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002478
2479 # http://b/18015246
2480 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2481 # for files larger than 2GiB. We can work around this by adjusting their
2482 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2483 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2484 # it isn't clear to me exactly what circumstances cause this).
2485 # `zipfile.write()` must be used directly to work around this.
2486 #
2487 # This mess can be avoided if we port to python3.
2488 saved_zip64_limit = zipfile.ZIP64_LIMIT
2489 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2490
2491 if compress_type is None:
2492 compress_type = zip_file.compression
2493 if arcname is None:
2494 arcname = filename
2495
2496 saved_stat = os.stat(filename)
2497
2498 try:
2499 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2500 # file to be zipped and reset it when we're done.
2501 os.chmod(filename, perms)
2502
2503 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002504 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2505 # intentional. zip stores datetimes in local time without a time zone
2506 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2507 # in the zip archive.
2508 local_epoch = datetime.datetime.fromtimestamp(0)
2509 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002510 os.utime(filename, (timestamp, timestamp))
2511
2512 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2513 finally:
2514 os.chmod(filename, saved_stat.st_mode)
2515 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2516 zipfile.ZIP64_LIMIT = saved_zip64_limit
2517
2518
Tao Bao58c1b962015-05-20 09:32:18 -07002519def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002520 compress_type=None):
2521 """Wrap zipfile.writestr() function to work around the zip64 limit.
2522
2523 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2524 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2525 when calling crc32(bytes).
2526
2527 But it still works fine to write a shorter string into a large zip file.
2528 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2529 when we know the string won't be too long.
2530 """
2531
2532 saved_zip64_limit = zipfile.ZIP64_LIMIT
2533 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2534
2535 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2536 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002537 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002538 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002539 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002540 else:
Tao Baof3282b42015-04-01 11:21:55 -07002541 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002542 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2543 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2544 # such a case (since
2545 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2546 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2547 # permission bits. We follow the logic in Python 3 to get consistent
2548 # behavior between using the two versions.
2549 if not zinfo.external_attr:
2550 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002551
2552 # If compress_type is given, it overrides the value in zinfo.
2553 if compress_type is not None:
2554 zinfo.compress_type = compress_type
2555
Tao Bao58c1b962015-05-20 09:32:18 -07002556 # If perms is given, it has a priority.
2557 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002558 # If perms doesn't set the file type, mark it as a regular file.
2559 if perms & 0o770000 == 0:
2560 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002561 zinfo.external_attr = perms << 16
2562
Tao Baof3282b42015-04-01 11:21:55 -07002563 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002564 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2565
Dan Albert8b72aef2015-03-23 19:13:21 -07002566 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002567 zipfile.ZIP64_LIMIT = saved_zip64_limit
2568
2569
Tao Bao89d7ab22017-12-14 17:05:33 -08002570def ZipDelete(zip_filename, entries):
2571 """Deletes entries from a ZIP file.
2572
2573 Since deleting entries from a ZIP file is not supported, it shells out to
2574 'zip -d'.
2575
2576 Args:
2577 zip_filename: The name of the ZIP file.
2578 entries: The name of the entry, or the list of names to be deleted.
2579
2580 Raises:
2581 AssertionError: In case of non-zero return from 'zip'.
2582 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002583 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002584 entries = [entries]
2585 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002586 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002587
2588
Tao Baof3282b42015-04-01 11:21:55 -07002589def ZipClose(zip_file):
2590 # http://b/18015246
2591 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2592 # central directory.
2593 saved_zip64_limit = zipfile.ZIP64_LIMIT
2594 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2595
2596 zip_file.close()
2597
2598 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002599
2600
2601class DeviceSpecificParams(object):
2602 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002603
Doug Zongker05d3dea2009-06-22 11:32:31 -07002604 def __init__(self, **kwargs):
2605 """Keyword arguments to the constructor become attributes of this
2606 object, which is passed to all functions in the device-specific
2607 module."""
Tao Bao38884282019-07-10 22:20:56 -07002608 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002609 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002610 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002611
2612 if self.module is None:
2613 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002614 if not path:
2615 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002616 try:
2617 if os.path.isdir(path):
2618 info = imp.find_module("releasetools", [path])
2619 else:
2620 d, f = os.path.split(path)
2621 b, x = os.path.splitext(f)
2622 if x == ".py":
2623 f = b
2624 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002625 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002626 self.module = imp.load_module("device_specific", *info)
2627 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002628 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002629
2630 def _DoCall(self, function_name, *args, **kwargs):
2631 """Call the named function in the device-specific module, passing
2632 the given args and kwargs. The first argument to the call will be
2633 the DeviceSpecific object itself. If there is no module, or the
2634 module does not define the function, return the value of the
2635 'default' kwarg (which itself defaults to None)."""
2636 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002637 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002638 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2639
2640 def FullOTA_Assertions(self):
2641 """Called after emitting the block of assertions at the top of a
2642 full OTA package. Implementations can add whatever additional
2643 assertions they like."""
2644 return self._DoCall("FullOTA_Assertions")
2645
Doug Zongkere5ff5902012-01-17 10:55:37 -08002646 def FullOTA_InstallBegin(self):
2647 """Called at the start of full OTA installation."""
2648 return self._DoCall("FullOTA_InstallBegin")
2649
Yifan Hong10c530d2018-12-27 17:34:18 -08002650 def FullOTA_GetBlockDifferences(self):
2651 """Called during full OTA installation and verification.
2652 Implementation should return a list of BlockDifference objects describing
2653 the update on each additional partitions.
2654 """
2655 return self._DoCall("FullOTA_GetBlockDifferences")
2656
Doug Zongker05d3dea2009-06-22 11:32:31 -07002657 def FullOTA_InstallEnd(self):
2658 """Called at the end of full OTA installation; typically this is
2659 used to install the image for the device's baseband processor."""
2660 return self._DoCall("FullOTA_InstallEnd")
2661
2662 def IncrementalOTA_Assertions(self):
2663 """Called after emitting the block of assertions at the top of an
2664 incremental OTA package. Implementations can add whatever
2665 additional assertions they like."""
2666 return self._DoCall("IncrementalOTA_Assertions")
2667
Doug Zongkere5ff5902012-01-17 10:55:37 -08002668 def IncrementalOTA_VerifyBegin(self):
2669 """Called at the start of the verification phase of incremental
2670 OTA installation; additional checks can be placed here to abort
2671 the script before any changes are made."""
2672 return self._DoCall("IncrementalOTA_VerifyBegin")
2673
Doug Zongker05d3dea2009-06-22 11:32:31 -07002674 def IncrementalOTA_VerifyEnd(self):
2675 """Called at the end of the verification phase of incremental OTA
2676 installation; additional checks can be placed here to abort the
2677 script before any changes are made."""
2678 return self._DoCall("IncrementalOTA_VerifyEnd")
2679
Doug Zongkere5ff5902012-01-17 10:55:37 -08002680 def IncrementalOTA_InstallBegin(self):
2681 """Called at the start of incremental OTA installation (after
2682 verification is complete)."""
2683 return self._DoCall("IncrementalOTA_InstallBegin")
2684
Yifan Hong10c530d2018-12-27 17:34:18 -08002685 def IncrementalOTA_GetBlockDifferences(self):
2686 """Called during incremental OTA installation and verification.
2687 Implementation should return a list of BlockDifference objects describing
2688 the update on each additional partitions.
2689 """
2690 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2691
Doug Zongker05d3dea2009-06-22 11:32:31 -07002692 def IncrementalOTA_InstallEnd(self):
2693 """Called at the end of incremental OTA installation; typically
2694 this is used to install the image for the device's baseband
2695 processor."""
2696 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002697
Tao Bao9bc6bb22015-11-09 16:58:28 -08002698 def VerifyOTA_Assertions(self):
2699 return self._DoCall("VerifyOTA_Assertions")
2700
Tao Bao76def242017-11-21 09:25:31 -08002701
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002702class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002703 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002704 self.name = name
2705 self.data = data
2706 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002707 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002708 self.sha1 = sha1(data).hexdigest()
2709
2710 @classmethod
2711 def FromLocalFile(cls, name, diskname):
2712 f = open(diskname, "rb")
2713 data = f.read()
2714 f.close()
2715 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002716
2717 def WriteToTemp(self):
2718 t = tempfile.NamedTemporaryFile()
2719 t.write(self.data)
2720 t.flush()
2721 return t
2722
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002723 def WriteToDir(self, d):
2724 with open(os.path.join(d, self.name), "wb") as fp:
2725 fp.write(self.data)
2726
Geremy Condra36bd3652014-02-06 19:45:10 -08002727 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002728 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002729
Tao Bao76def242017-11-21 09:25:31 -08002730
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002731DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002732 ".gz": "imgdiff",
2733 ".zip": ["imgdiff", "-z"],
2734 ".jar": ["imgdiff", "-z"],
2735 ".apk": ["imgdiff", "-z"],
2736 ".img": "imgdiff",
2737}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002738
Tao Bao76def242017-11-21 09:25:31 -08002739
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002740class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002741 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002742 self.tf = tf
2743 self.sf = sf
2744 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002745 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002746
2747 def ComputePatch(self):
2748 """Compute the patch (as a string of data) needed to turn sf into
2749 tf. Returns the same tuple as GetPatch()."""
2750
2751 tf = self.tf
2752 sf = self.sf
2753
Doug Zongker24cd2802012-08-14 16:36:15 -07002754 if self.diff_program:
2755 diff_program = self.diff_program
2756 else:
2757 ext = os.path.splitext(tf.name)[1]
2758 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002759
2760 ttemp = tf.WriteToTemp()
2761 stemp = sf.WriteToTemp()
2762
2763 ext = os.path.splitext(tf.name)[1]
2764
2765 try:
2766 ptemp = tempfile.NamedTemporaryFile()
2767 if isinstance(diff_program, list):
2768 cmd = copy.copy(diff_program)
2769 else:
2770 cmd = [diff_program]
2771 cmd.append(stemp.name)
2772 cmd.append(ttemp.name)
2773 cmd.append(ptemp.name)
2774 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002775 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002776
Doug Zongkerf8340082014-08-05 10:39:37 -07002777 def run():
2778 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002779 if e:
2780 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002781 th = threading.Thread(target=run)
2782 th.start()
2783 th.join(timeout=300) # 5 mins
2784 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002785 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002786 p.terminate()
2787 th.join(5)
2788 if th.is_alive():
2789 p.kill()
2790 th.join()
2791
Tianjie Xua2a9f992018-01-05 15:15:54 -08002792 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002793 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002794 self.patch = None
2795 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002796 diff = ptemp.read()
2797 finally:
2798 ptemp.close()
2799 stemp.close()
2800 ttemp.close()
2801
2802 self.patch = diff
2803 return self.tf, self.sf, self.patch
2804
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002805 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002806 """Returns a tuple of (target_file, source_file, patch_data).
2807
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002808 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002809 computing the patch failed.
2810 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002811 return self.tf, self.sf, self.patch
2812
2813
2814def ComputeDifferences(diffs):
2815 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002816 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002817
2818 # Do the largest files first, to try and reduce the long-pole effect.
2819 by_size = [(i.tf.size, i) for i in diffs]
2820 by_size.sort(reverse=True)
2821 by_size = [i[1] for i in by_size]
2822
2823 lock = threading.Lock()
2824 diff_iter = iter(by_size) # accessed under lock
2825
2826 def worker():
2827 try:
2828 lock.acquire()
2829 for d in diff_iter:
2830 lock.release()
2831 start = time.time()
2832 d.ComputePatch()
2833 dur = time.time() - start
2834 lock.acquire()
2835
2836 tf, sf, patch = d.GetPatch()
2837 if sf.name == tf.name:
2838 name = tf.name
2839 else:
2840 name = "%s (%s)" % (tf.name, sf.name)
2841 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002842 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002843 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002844 logger.info(
2845 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2846 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002847 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002848 except Exception:
2849 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002850 raise
2851
2852 # start worker threads; wait for them all to finish.
2853 threads = [threading.Thread(target=worker)
2854 for i in range(OPTIONS.worker_threads)]
2855 for th in threads:
2856 th.start()
2857 while threads:
2858 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002859
2860
Dan Albert8b72aef2015-03-23 19:13:21 -07002861class BlockDifference(object):
2862 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002863 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002864 self.tgt = tgt
2865 self.src = src
2866 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002867 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002868 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002869
Tao Baodd2a5892015-03-12 12:32:37 -07002870 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002871 version = max(
2872 int(i) for i in
2873 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002874 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002875 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002876
Tianjie Xu41976c72019-07-03 13:57:01 -07002877 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2878 version=self.version,
2879 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002880 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002881 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002882 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002883 self.touched_src_ranges = b.touched_src_ranges
2884 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002885
Yifan Hong10c530d2018-12-27 17:34:18 -08002886 # On devices with dynamic partitions, for new partitions,
2887 # src is None but OPTIONS.source_info_dict is not.
2888 if OPTIONS.source_info_dict is None:
2889 is_dynamic_build = OPTIONS.info_dict.get(
2890 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002891 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002892 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002893 is_dynamic_build = OPTIONS.source_info_dict.get(
2894 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002895 is_dynamic_source = partition in shlex.split(
2896 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002897
Yifan Hongbb2658d2019-01-25 12:30:58 -08002898 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002899 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2900
Yifan Hongbb2658d2019-01-25 12:30:58 -08002901 # For dynamic partitions builds, check partition list in both source
2902 # and target build because new partitions may be added, and existing
2903 # partitions may be removed.
2904 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2905
Yifan Hong10c530d2018-12-27 17:34:18 -08002906 if is_dynamic:
2907 self.device = 'map_partition("%s")' % partition
2908 else:
2909 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07002910 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2911 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08002912 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07002913 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
2914 OPTIONS.source_info_dict)
2915 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002916
Tao Baod8d14be2016-02-04 14:26:02 -08002917 @property
2918 def required_cache(self):
2919 return self._required_cache
2920
Tao Bao76def242017-11-21 09:25:31 -08002921 def WriteScript(self, script, output_zip, progress=None,
2922 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002923 if not self.src:
2924 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002925 script.Print("Patching %s image unconditionally..." % (self.partition,))
2926 else:
2927 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002928
Dan Albert8b72aef2015-03-23 19:13:21 -07002929 if progress:
2930 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002931 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002932
2933 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002934 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002935
Tao Bao9bc6bb22015-11-09 16:58:28 -08002936 def WriteStrictVerifyScript(self, script):
2937 """Verify all the blocks in the care_map, including clobbered blocks.
2938
2939 This differs from the WriteVerifyScript() function: a) it prints different
2940 error messages; b) it doesn't allow half-way updated images to pass the
2941 verification."""
2942
2943 partition = self.partition
2944 script.Print("Verifying %s..." % (partition,))
2945 ranges = self.tgt.care_map
2946 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002947 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002948 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2949 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002950 self.device, ranges_str,
2951 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002952 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002953 script.AppendExtra("")
2954
Tao Baod522bdc2016-04-12 15:53:16 -07002955 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002956 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002957
2958 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002959 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002960 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002961
2962 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002963 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002964 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002965 ranges = self.touched_src_ranges
2966 expected_sha1 = self.touched_src_sha1
2967 else:
2968 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2969 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002970
2971 # No blocks to be checked, skipping.
2972 if not ranges:
2973 return
2974
Tao Bao5ece99d2015-05-12 11:42:31 -07002975 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002976 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002977 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002978 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2979 '"%s.patch.dat")) then' % (
2980 self.device, ranges_str, expected_sha1,
2981 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002982 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002983 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002984
Tianjie Xufc3422a2015-12-15 11:53:59 -08002985 if self.version >= 4:
2986
2987 # Bug: 21124327
2988 # When generating incrementals for the system and vendor partitions in
2989 # version 4 or newer, explicitly check the first block (which contains
2990 # the superblock) of the partition to see if it's what we expect. If
2991 # this check fails, give an explicit log message about the partition
2992 # having been remounted R/W (the most likely explanation).
2993 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002994 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002995
2996 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002997 if partition == "system":
2998 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2999 else:
3000 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003001 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003002 'ifelse (block_image_recover({device}, "{ranges}") && '
3003 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003004 'package_extract_file("{partition}.transfer.list"), '
3005 '"{partition}.new.dat", "{partition}.patch.dat"), '
3006 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003007 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003008 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003009 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003010
Tao Baodd2a5892015-03-12 12:32:37 -07003011 # Abort the OTA update. Note that the incremental OTA cannot be applied
3012 # even if it may match the checksum of the target partition.
3013 # a) If version < 3, operations like move and erase will make changes
3014 # unconditionally and damage the partition.
3015 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003016 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003017 if partition == "system":
3018 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3019 else:
3020 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3021 script.AppendExtra((
3022 'abort("E%d: %s partition has unexpected contents");\n'
3023 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003024
Yifan Hong10c530d2018-12-27 17:34:18 -08003025 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003026 partition = self.partition
3027 script.Print('Verifying the updated %s image...' % (partition,))
3028 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3029 ranges = self.tgt.care_map
3030 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003031 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003032 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003033 self.device, ranges_str,
3034 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003035
3036 # Bug: 20881595
3037 # Verify that extended blocks are really zeroed out.
3038 if self.tgt.extended:
3039 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003040 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003041 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003042 self.device, ranges_str,
3043 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003044 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003045 if partition == "system":
3046 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3047 else:
3048 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003049 script.AppendExtra(
3050 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003051 ' abort("E%d: %s partition has unexpected non-zero contents after '
3052 'OTA update");\n'
3053 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003054 else:
3055 script.Print('Verified the updated %s image.' % (partition,))
3056
Tianjie Xu209db462016-05-24 17:34:52 -07003057 if partition == "system":
3058 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3059 else:
3060 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3061
Tao Bao5fcaaef2015-06-01 13:40:49 -07003062 script.AppendExtra(
3063 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003064 ' abort("E%d: %s partition has unexpected contents after OTA '
3065 'update");\n'
3066 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003067
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003068 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003069 ZipWrite(output_zip,
3070 '{}.transfer.list'.format(self.path),
3071 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003072
Tao Bao76def242017-11-21 09:25:31 -08003073 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3074 # its size. Quailty 9 almost triples the compression time but doesn't
3075 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003076 # zip | brotli(quality 6) | brotli(quality 9)
3077 # compressed_size: 942M | 869M (~8% reduced) | 854M
3078 # compression_time: 75s | 265s | 719s
3079 # decompression_time: 15s | 25s | 25s
3080
3081 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003082 brotli_cmd = ['brotli', '--quality=6',
3083 '--output={}.new.dat.br'.format(self.path),
3084 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003085 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003086 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003087
3088 new_data_name = '{}.new.dat.br'.format(self.partition)
3089 ZipWrite(output_zip,
3090 '{}.new.dat.br'.format(self.path),
3091 new_data_name,
3092 compress_type=zipfile.ZIP_STORED)
3093 else:
3094 new_data_name = '{}.new.dat'.format(self.partition)
3095 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3096
Dan Albert8e0178d2015-01-27 15:53:15 -08003097 ZipWrite(output_zip,
3098 '{}.patch.dat'.format(self.path),
3099 '{}.patch.dat'.format(self.partition),
3100 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003101
Tianjie Xu209db462016-05-24 17:34:52 -07003102 if self.partition == "system":
3103 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3104 else:
3105 code = ErrorCode.VENDOR_UPDATE_FAILURE
3106
Yifan Hong10c530d2018-12-27 17:34:18 -08003107 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003108 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003109 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003110 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003111 device=self.device, partition=self.partition,
3112 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003113 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003114
Kelvin Zhang0876c412020-06-23 15:06:58 -04003115 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003116 data = source.ReadRangeSet(ranges)
3117 ctx = sha1()
3118
3119 for p in data:
3120 ctx.update(p)
3121
3122 return ctx.hexdigest()
3123
Kelvin Zhang0876c412020-06-23 15:06:58 -04003124 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003125 """Return the hash value for all zero blocks."""
3126 zero_block = '\x00' * 4096
3127 ctx = sha1()
3128 for _ in range(num_blocks):
3129 ctx.update(zero_block)
3130
3131 return ctx.hexdigest()
3132
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003133
Tianjie Xu41976c72019-07-03 13:57:01 -07003134# Expose these two classes to support vendor-specific scripts
3135DataImage = images.DataImage
3136EmptyImage = images.EmptyImage
3137
Tao Bao76def242017-11-21 09:25:31 -08003138
Doug Zongker96a57e72010-09-26 14:57:41 -07003139# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003140PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003141 "ext4": "EMMC",
3142 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003143 "f2fs": "EMMC",
3144 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003145}
Doug Zongker96a57e72010-09-26 14:57:41 -07003146
Kelvin Zhang0876c412020-06-23 15:06:58 -04003147
Yifan Hongbdb32012020-05-07 12:38:53 -07003148def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3149 """
3150 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3151 backwards compatibility. It aborts if the fstab entry has slotselect option
3152 (unless check_no_slot is explicitly set to False).
3153 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003154 fstab = info["fstab"]
3155 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003156 if check_no_slot:
3157 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003158 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003159 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3160 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003161 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003162
3163
Yifan Hongbdb32012020-05-07 12:38:53 -07003164def GetTypeAndDeviceExpr(mount_point, info):
3165 """
3166 Return the filesystem of the partition, and an edify expression that evaluates
3167 to the device at runtime.
3168 """
3169 fstab = info["fstab"]
3170 if fstab:
3171 p = fstab[mount_point]
3172 device_expr = '"%s"' % fstab[mount_point].device
3173 if p.slotselect:
3174 device_expr = 'add_slot_suffix(%s)' % device_expr
3175 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003176 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003177
3178
3179def GetEntryForDevice(fstab, device):
3180 """
3181 Returns:
3182 The first entry in fstab whose device is the given value.
3183 """
3184 if not fstab:
3185 return None
3186 for mount_point in fstab:
3187 if fstab[mount_point].device == device:
3188 return fstab[mount_point]
3189 return None
3190
Kelvin Zhang0876c412020-06-23 15:06:58 -04003191
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003192def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003193 """Parses and converts a PEM-encoded certificate into DER-encoded.
3194
3195 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3196
3197 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003198 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003199 """
3200 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003201 save = False
3202 for line in data.split("\n"):
3203 if "--END CERTIFICATE--" in line:
3204 break
3205 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003206 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003207 if "--BEGIN CERTIFICATE--" in line:
3208 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003209 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003210 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003211
Tao Bao04e1f012018-02-04 12:13:35 -08003212
3213def ExtractPublicKey(cert):
3214 """Extracts the public key (PEM-encoded) from the given certificate file.
3215
3216 Args:
3217 cert: The certificate filename.
3218
3219 Returns:
3220 The public key string.
3221
3222 Raises:
3223 AssertionError: On non-zero return from 'openssl'.
3224 """
3225 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3226 # While openssl 1.1 writes the key into the given filename followed by '-out',
3227 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3228 # stdout instead.
3229 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3230 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3231 pubkey, stderrdata = proc.communicate()
3232 assert proc.returncode == 0, \
3233 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3234 return pubkey
3235
3236
Tao Bao1ac886e2019-06-26 11:58:22 -07003237def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003238 """Extracts the AVB public key from the given public or private key.
3239
3240 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003241 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003242 key: The input key file, which should be PEM-encoded public or private key.
3243
3244 Returns:
3245 The path to the extracted AVB public key file.
3246 """
3247 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3248 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003249 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003250 return output
3251
3252
Doug Zongker412c02f2014-02-13 10:58:24 -08003253def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3254 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003255 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003256
Tao Bao6d5d6232018-03-09 17:04:42 -08003257 Most of the space in the boot and recovery images is just the kernel, which is
3258 identical for the two, so the resulting patch should be efficient. Add it to
3259 the output zip, along with a shell script that is run from init.rc on first
3260 boot to actually do the patching and install the new recovery image.
3261
3262 Args:
3263 input_dir: The top-level input directory of the target-files.zip.
3264 output_sink: The callback function that writes the result.
3265 recovery_img: File object for the recovery image.
3266 boot_img: File objects for the boot image.
3267 info_dict: A dict returned by common.LoadInfoDict() on the input
3268 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003269 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003270 if info_dict is None:
3271 info_dict = OPTIONS.info_dict
3272
Tao Bao6d5d6232018-03-09 17:04:42 -08003273 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003274 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3275
3276 if board_uses_vendorimage:
3277 # In this case, the output sink is rooted at VENDOR
3278 recovery_img_path = "etc/recovery.img"
3279 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3280 sh_dir = "bin"
3281 else:
3282 # In this case the output sink is rooted at SYSTEM
3283 recovery_img_path = "vendor/etc/recovery.img"
3284 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3285 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003286
Tao Baof2cffbd2015-07-22 12:33:18 -07003287 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003288 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003289
3290 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003291 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003292 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003293 # With system-root-image, boot and recovery images will have mismatching
3294 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3295 # to handle such a case.
3296 if system_root_image:
3297 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003298 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003299 assert not os.path.exists(path)
3300 else:
3301 diff_program = ["imgdiff"]
3302 if os.path.exists(path):
3303 diff_program.append("-b")
3304 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003305 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003306 else:
3307 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003308
3309 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3310 _, _, patch = d.ComputePatch()
3311 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003312
Dan Albertebb19aa2015-03-27 19:11:53 -07003313 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003314 # The following GetTypeAndDevice()s need to use the path in the target
3315 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003316 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3317 check_no_slot=False)
3318 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3319 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003320 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003321 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003322
Tao Baof2cffbd2015-07-22 12:33:18 -07003323 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003324
3325 # Note that we use /vendor to refer to the recovery resources. This will
3326 # work for a separate vendor partition mounted at /vendor or a
3327 # /system/vendor subdirectory on the system partition, for which init will
3328 # create a symlink from /vendor to /system/vendor.
3329
3330 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003331if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3332 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003333 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003334 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3335 log -t recovery "Installing new recovery image: succeeded" || \\
3336 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003337else
3338 log -t recovery "Recovery image already installed"
3339fi
3340""" % {'type': recovery_type,
3341 'device': recovery_device,
3342 'sha1': recovery_img.sha1,
3343 'size': recovery_img.size}
3344 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003345 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003346if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3347 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003348 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003349 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3350 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3351 log -t recovery "Installing new recovery image: succeeded" || \\
3352 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003353else
3354 log -t recovery "Recovery image already installed"
3355fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003356""" % {'boot_size': boot_img.size,
3357 'boot_sha1': boot_img.sha1,
3358 'recovery_size': recovery_img.size,
3359 'recovery_sha1': recovery_img.sha1,
3360 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003361 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003362 'recovery_type': recovery_type,
3363 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003364 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003365
Bill Peckhame868aec2019-09-17 17:06:47 -07003366 # The install script location moved from /system/etc to /system/bin in the L
3367 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3368 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003369
Tao Bao32fcdab2018-10-12 10:30:39 -07003370 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003371
Tao Baoda30cfa2017-12-01 16:19:46 -08003372 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003373
3374
3375class DynamicPartitionUpdate(object):
3376 def __init__(self, src_group=None, tgt_group=None, progress=None,
3377 block_difference=None):
3378 self.src_group = src_group
3379 self.tgt_group = tgt_group
3380 self.progress = progress
3381 self.block_difference = block_difference
3382
3383 @property
3384 def src_size(self):
3385 if not self.block_difference:
3386 return 0
3387 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3388
3389 @property
3390 def tgt_size(self):
3391 if not self.block_difference:
3392 return 0
3393 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3394
3395 @staticmethod
3396 def _GetSparseImageSize(img):
3397 if not img:
3398 return 0
3399 return img.blocksize * img.total_blocks
3400
3401
3402class DynamicGroupUpdate(object):
3403 def __init__(self, src_size=None, tgt_size=None):
3404 # None: group does not exist. 0: no size limits.
3405 self.src_size = src_size
3406 self.tgt_size = tgt_size
3407
3408
3409class DynamicPartitionsDifference(object):
3410 def __init__(self, info_dict, block_diffs, progress_dict=None,
3411 source_info_dict=None):
3412 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003413 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003414
3415 self._remove_all_before_apply = False
3416 if source_info_dict is None:
3417 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003418 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003419
Tao Baof1113e92019-06-18 12:10:14 -07003420 block_diff_dict = collections.OrderedDict(
3421 [(e.partition, e) for e in block_diffs])
3422
Yifan Hong10c530d2018-12-27 17:34:18 -08003423 assert len(block_diff_dict) == len(block_diffs), \
3424 "Duplicated BlockDifference object for {}".format(
3425 [partition for partition, count in
3426 collections.Counter(e.partition for e in block_diffs).items()
3427 if count > 1])
3428
Yifan Hong79997e52019-01-23 16:56:19 -08003429 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003430
3431 for p, block_diff in block_diff_dict.items():
3432 self._partition_updates[p] = DynamicPartitionUpdate()
3433 self._partition_updates[p].block_difference = block_diff
3434
3435 for p, progress in progress_dict.items():
3436 if p in self._partition_updates:
3437 self._partition_updates[p].progress = progress
3438
3439 tgt_groups = shlex.split(info_dict.get(
3440 "super_partition_groups", "").strip())
3441 src_groups = shlex.split(source_info_dict.get(
3442 "super_partition_groups", "").strip())
3443
3444 for g in tgt_groups:
3445 for p in shlex.split(info_dict.get(
3446 "super_%s_partition_list" % g, "").strip()):
3447 assert p in self._partition_updates, \
3448 "{} is in target super_{}_partition_list but no BlockDifference " \
3449 "object is provided.".format(p, g)
3450 self._partition_updates[p].tgt_group = g
3451
3452 for g in src_groups:
3453 for p in shlex.split(source_info_dict.get(
3454 "super_%s_partition_list" % g, "").strip()):
3455 assert p in self._partition_updates, \
3456 "{} is in source super_{}_partition_list but no BlockDifference " \
3457 "object is provided.".format(p, g)
3458 self._partition_updates[p].src_group = g
3459
Yifan Hong45433e42019-01-18 13:55:25 -08003460 target_dynamic_partitions = set(shlex.split(info_dict.get(
3461 "dynamic_partition_list", "").strip()))
3462 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3463 if u.tgt_size)
3464 assert block_diffs_with_target == target_dynamic_partitions, \
3465 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3466 list(target_dynamic_partitions), list(block_diffs_with_target))
3467
3468 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3469 "dynamic_partition_list", "").strip()))
3470 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3471 if u.src_size)
3472 assert block_diffs_with_source == source_dynamic_partitions, \
3473 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3474 list(source_dynamic_partitions), list(block_diffs_with_source))
3475
Yifan Hong10c530d2018-12-27 17:34:18 -08003476 if self._partition_updates:
3477 logger.info("Updating dynamic partitions %s",
3478 self._partition_updates.keys())
3479
Yifan Hong79997e52019-01-23 16:56:19 -08003480 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003481
3482 for g in tgt_groups:
3483 self._group_updates[g] = DynamicGroupUpdate()
3484 self._group_updates[g].tgt_size = int(info_dict.get(
3485 "super_%s_group_size" % g, "0").strip())
3486
3487 for g in src_groups:
3488 if g not in self._group_updates:
3489 self._group_updates[g] = DynamicGroupUpdate()
3490 self._group_updates[g].src_size = int(source_info_dict.get(
3491 "super_%s_group_size" % g, "0").strip())
3492
3493 self._Compute()
3494
3495 def WriteScript(self, script, output_zip, write_verify_script=False):
3496 script.Comment('--- Start patching dynamic partitions ---')
3497 for p, u in self._partition_updates.items():
3498 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3499 script.Comment('Patch partition %s' % p)
3500 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3501 write_verify_script=False)
3502
3503 op_list_path = MakeTempFile()
3504 with open(op_list_path, 'w') as f:
3505 for line in self._op_list:
3506 f.write('{}\n'.format(line))
3507
3508 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3509
3510 script.Comment('Update dynamic partition metadata')
3511 script.AppendExtra('assert(update_dynamic_partitions('
3512 'package_extract_file("dynamic_partitions_op_list")));')
3513
3514 if write_verify_script:
3515 for p, u in self._partition_updates.items():
3516 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3517 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003518 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003519
3520 for p, u in self._partition_updates.items():
3521 if u.tgt_size and u.src_size <= u.tgt_size:
3522 script.Comment('Patch partition %s' % p)
3523 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3524 write_verify_script=write_verify_script)
3525 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003526 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003527
3528 script.Comment('--- End patching dynamic partitions ---')
3529
3530 def _Compute(self):
3531 self._op_list = list()
3532
3533 def append(line):
3534 self._op_list.append(line)
3535
3536 def comment(line):
3537 self._op_list.append("# %s" % line)
3538
3539 if self._remove_all_before_apply:
3540 comment('Remove all existing dynamic partitions and groups before '
3541 'applying full OTA')
3542 append('remove_all_groups')
3543
3544 for p, u in self._partition_updates.items():
3545 if u.src_group and not u.tgt_group:
3546 append('remove %s' % p)
3547
3548 for p, u in self._partition_updates.items():
3549 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3550 comment('Move partition %s from %s to default' % (p, u.src_group))
3551 append('move %s default' % p)
3552
3553 for p, u in self._partition_updates.items():
3554 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3555 comment('Shrink partition %s from %d to %d' %
3556 (p, u.src_size, u.tgt_size))
3557 append('resize %s %s' % (p, u.tgt_size))
3558
3559 for g, u in self._group_updates.items():
3560 if u.src_size is not None and u.tgt_size is None:
3561 append('remove_group %s' % g)
3562 if (u.src_size is not None and u.tgt_size is not None and
3563 u.src_size > u.tgt_size):
3564 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3565 append('resize_group %s %d' % (g, u.tgt_size))
3566
3567 for g, u in self._group_updates.items():
3568 if u.src_size is None and u.tgt_size is not None:
3569 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3570 append('add_group %s %d' % (g, u.tgt_size))
3571 if (u.src_size is not None and u.tgt_size is not None and
3572 u.src_size < u.tgt_size):
3573 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3574 append('resize_group %s %d' % (g, u.tgt_size))
3575
3576 for p, u in self._partition_updates.items():
3577 if u.tgt_group and not u.src_group:
3578 comment('Add partition %s to group %s' % (p, u.tgt_group))
3579 append('add %s %s' % (p, u.tgt_group))
3580
3581 for p, u in self._partition_updates.items():
3582 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003583 comment('Grow partition %s from %d to %d' %
3584 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003585 append('resize %s %d' % (p, u.tgt_size))
3586
3587 for p, u in self._partition_updates.items():
3588 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3589 comment('Move partition %s from default to %s' %
3590 (p, u.tgt_group))
3591 append('move %s %s' % (p, u.tgt_group))