blob: 83425cc8d683a2b5d03b52d0fb3df216056b1aa1 [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
Kelvin Zhang27324132021-03-22 15:38:38 -040044import rangelib
Tao Baoc765cca2018-01-31 17:32:40 -080045import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070046from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070047
Tao Bao32fcdab2018-10-12 10:30:39 -070048logger = logging.getLogger(__name__)
49
Tao Bao986ee862018-10-04 15:46:16 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070052
Dan Albert8b72aef2015-03-23 19:13:21 -070053 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070054 # Set up search path, in order to find framework/ and lib64/. At the time of
55 # running this function, user-supplied search path (`--path`) hasn't been
56 # available. So the value set here is the default, which might be overridden
57 # by commandline flag later.
Kelvin Zhang0876c412020-06-23 15:06:58 -040058 exec_path = os.path.realpath(sys.argv[0])
Tao Baoafd92a82019-10-10 22:44:22 -070059 if exec_path.endswith('.py'):
60 script_name = os.path.basename(exec_path)
61 # logger hasn't been initialized yet at this point. Use print to output
62 # warnings.
63 print(
64 'Warning: releasetools script should be invoked as hermetic Python '
Kelvin Zhang0876c412020-06-23 15:06:58 -040065 'executable -- build and run `{}` directly.'.format(
66 script_name[:-3]),
Tao Baoafd92a82019-10-10 22:44:22 -070067 file=sys.stderr)
Kelvin Zhang0876c412020-06-23 15:06:58 -040068 self.search_path = os.path.dirname(os.path.dirname(exec_path))
Pavel Salomatov32676552019-03-06 20:00:45 +030069
Dan Albert8b72aef2015-03-23 19:13:21 -070070 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080071 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070072 self.extra_signapk_args = []
73 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080074 self.java_args = ["-Xmx2048m"] # The default JVM args.
Tianjie Xu88a759d2020-01-23 10:47:54 -080075 self.android_jar_path = None
Dan Albert8b72aef2015-03-23 19:13:21 -070076 self.public_key_suffix = ".x509.pem"
77 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070078 # use otatools built boot_signer by default
79 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070080 self.boot_signer_args = []
81 self.verity_signer_path = None
82 self.verity_signer_args = []
Tianjie0f307452020-04-01 12:20:21 -070083 self.aftl_tool_path = None
Dan Austin52903642019-12-12 15:44:00 -080084 self.aftl_server = None
85 self.aftl_key_path = None
86 self.aftl_manufacturer_key_path = None
87 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070088 self.verbose = False
89 self.tempfiles = []
90 self.device_specific = None
91 self.extras = {}
92 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070093 self.source_info_dict = None
94 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070095 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070096 # Stash size cannot exceed cache_size * threshold.
97 self.cache_size = None
98 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070099 self.logfile = None
Yifan Hong8e332ff2020-07-29 17:51:55 -0700100 self.host_tools = {}
Dan Albert8b72aef2015-03-23 19:13:21 -0700101
102
103OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -0700104
Tao Bao71197512018-10-11 14:08:45 -0700105# The block size that's used across the releasetools scripts.
106BLOCK_SIZE = 4096
107
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800108# Values for "certificate" in apkcerts that mean special things.
109SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
110
Tao Bao5cc0abb2019-03-21 10:18:05 -0700111# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
112# that system_other is not in the list because we don't want to include its
Tianjiebf0b8a82021-03-03 17:31:04 -0800113# descriptor into vbmeta.img. When adding a new entry here, the
114# AVB_FOOTER_ARGS_BY_PARTITION in sign_target_files_apks need to be updated
115# accordingly.
Andrew Sculle077cf72021-02-18 10:27:29 +0000116AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'pvmfw', 'recovery',
117 'system', 'system_ext', 'vendor', 'vendor_boot',
118 'vendor_dlkm', 'odm_dlkm')
Tao Bao9dd909e2017-11-14 11:27:32 -0800119
Tao Bao08c190f2019-06-03 23:07:58 -0700120# Chained VBMeta partitions.
121AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
122
Tianjie Xu861f4132018-09-12 11:49:33 -0700123# Partitions that should have their care_map added to META/care_map.pb
Kelvin Zhang39aea442020-08-17 11:04:25 -0400124PARTITIONS_WITH_CARE_MAP = [
Yifan Hongcfb917a2020-05-07 14:58:20 -0700125 'system',
126 'vendor',
127 'product',
128 'system_ext',
129 'odm',
130 'vendor_dlkm',
Yifan Hongf496f1b2020-07-15 16:52:59 -0700131 'odm_dlkm',
Kelvin Zhang39aea442020-08-17 11:04:25 -0400132]
Tianjie Xu861f4132018-09-12 11:49:33 -0700133
Yifan Hong5057b952021-01-07 14:09:57 -0800134# Partitions with a build.prop file
Yifan Hong10482a22021-01-07 14:38:41 -0800135PARTITIONS_WITH_BUILD_PROP = PARTITIONS_WITH_CARE_MAP + ['boot']
Yifan Hong5057b952021-01-07 14:09:57 -0800136
Yifan Hongc65a0542021-01-07 14:21:01 -0800137# See sysprop.mk. If file is moved, add new search paths here; don't remove
138# existing search paths.
139RAMDISK_BUILD_PROP_REL_PATHS = ['system/etc/ramdisk/build.prop']
Tianjie Xu861f4132018-09-12 11:49:33 -0700140
Tianjie Xu209db462016-05-24 17:34:52 -0700141class ErrorCode(object):
142 """Define error_codes for failures that happen during the actual
143 update package installation.
144
145 Error codes 0-999 are reserved for failures before the package
146 installation (i.e. low battery, package verification failure).
147 Detailed code in 'bootable/recovery/error_code.h' """
148
149 SYSTEM_VERIFICATION_FAILURE = 1000
150 SYSTEM_UPDATE_FAILURE = 1001
151 SYSTEM_UNEXPECTED_CONTENTS = 1002
152 SYSTEM_NONZERO_CONTENTS = 1003
153 SYSTEM_RECOVER_FAILURE = 1004
154 VENDOR_VERIFICATION_FAILURE = 2000
155 VENDOR_UPDATE_FAILURE = 2001
156 VENDOR_UNEXPECTED_CONTENTS = 2002
157 VENDOR_NONZERO_CONTENTS = 2003
158 VENDOR_RECOVER_FAILURE = 2004
159 OEM_PROP_MISMATCH = 3000
160 FINGERPRINT_MISMATCH = 3001
161 THUMBPRINT_MISMATCH = 3002
162 OLDER_BUILD = 3003
163 DEVICE_MISMATCH = 3004
164 BAD_PATCH_FILE = 3005
165 INSUFFICIENT_CACHE_SPACE = 3006
166 TUNE_PARTITION_FAILURE = 3007
167 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800168
Tao Bao80921982018-03-21 21:02:19 -0700169
Dan Albert8b72aef2015-03-23 19:13:21 -0700170class ExternalError(RuntimeError):
171 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700172
173
Tao Bao32fcdab2018-10-12 10:30:39 -0700174def InitLogging():
175 DEFAULT_LOGGING_CONFIG = {
176 'version': 1,
177 'disable_existing_loggers': False,
178 'formatters': {
179 'standard': {
180 'format':
181 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
182 'datefmt': '%Y-%m-%d %H:%M:%S',
183 },
184 },
185 'handlers': {
186 'default': {
187 'class': 'logging.StreamHandler',
188 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700189 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700190 },
191 },
192 'loggers': {
193 '': {
194 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700195 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700196 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700197 }
198 }
199 }
200 env_config = os.getenv('LOGGING_CONFIG')
201 if env_config:
202 with open(env_config) as f:
203 config = json.load(f)
204 else:
205 config = DEFAULT_LOGGING_CONFIG
206
207 # Increase the logging level for verbose mode.
208 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700209 config = copy.deepcopy(config)
210 config['handlers']['default']['level'] = 'INFO'
211
212 if OPTIONS.logfile:
213 config = copy.deepcopy(config)
214 config['handlers']['logfile'] = {
Kelvin Zhang0876c412020-06-23 15:06:58 -0400215 'class': 'logging.FileHandler',
216 'formatter': 'standard',
217 'level': 'INFO',
218 'mode': 'w',
219 'filename': OPTIONS.logfile,
Yifan Hong30910932019-10-25 20:36:55 -0700220 }
221 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700222
223 logging.config.dictConfig(config)
224
225
Yifan Hong8e332ff2020-07-29 17:51:55 -0700226def SetHostToolLocation(tool_name, location):
227 OPTIONS.host_tools[tool_name] = location
228
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900229def FindHostToolPath(tool_name):
230 """Finds the path to the host tool.
231
232 Args:
233 tool_name: name of the tool to find
234 Returns:
235 path to the tool if found under either one of the host_tools map or under
236 the same directory as this binary is located at. If not found, tool_name
237 is returned.
238 """
239 if tool_name in OPTIONS.host_tools:
240 return OPTIONS.host_tools[tool_name]
241
242 my_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
243 tool_path = os.path.join(my_dir, tool_name)
244 if os.path.exists(tool_path):
245 return tool_path
246
247 return tool_name
Yifan Hong8e332ff2020-07-29 17:51:55 -0700248
Tao Bao39451582017-05-04 11:10:47 -0700249def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700250 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700251
Tao Bao73dd4f42018-10-04 16:25:33 -0700252 Args:
253 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700254 verbose: Whether the commands should be shown. Default to the global
255 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700256 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
257 stdin, etc. stdout and stderr will default to subprocess.PIPE and
258 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800259 universal_newlines will default to True, as most of the users in
260 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700261
262 Returns:
263 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700264 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700265 if 'stdout' not in kwargs and 'stderr' not in kwargs:
266 kwargs['stdout'] = subprocess.PIPE
267 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800268 if 'universal_newlines' not in kwargs:
269 kwargs['universal_newlines'] = True
Yifan Hong8e332ff2020-07-29 17:51:55 -0700270
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900271 if args:
272 # Make a copy of args in case client relies on the content of args later.
Yifan Hong8e332ff2020-07-29 17:51:55 -0700273 args = args[:]
Jiyong Parkc8c94ac2020-11-20 03:03:57 +0900274 args[0] = FindHostToolPath(args[0])
Yifan Hong8e332ff2020-07-29 17:51:55 -0700275
Tao Bao32fcdab2018-10-12 10:30:39 -0700276 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400277 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700278 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700279 return subprocess.Popen(args, **kwargs)
280
281
Tao Bao986ee862018-10-04 15:46:16 -0700282def RunAndCheckOutput(args, verbose=None, **kwargs):
283 """Runs the given command and returns the output.
284
285 Args:
286 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700287 verbose: Whether the commands should be shown. Default to the global
288 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700289 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
290 stdin, etc. stdout and stderr will default to subprocess.PIPE and
291 subprocess.STDOUT respectively unless caller specifies any of them.
292
293 Returns:
294 The output string.
295
296 Raises:
297 ExternalError: On non-zero exit from the command.
298 """
Tao Bao986ee862018-10-04 15:46:16 -0700299 proc = Run(args, verbose=verbose, **kwargs)
300 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800301 if output is None:
302 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700303 # Don't log any if caller explicitly says so.
Kelvin Zhang0876c412020-06-23 15:06:58 -0400304 if verbose:
Tao Bao32fcdab2018-10-12 10:30:39 -0700305 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700306 if proc.returncode != 0:
307 raise ExternalError(
308 "Failed to run command '{}' (exit code {}):\n{}".format(
309 args, proc.returncode, output))
310 return output
311
312
Tao Baoc765cca2018-01-31 17:32:40 -0800313def RoundUpTo4K(value):
314 rounded_up = value + 4095
315 return rounded_up - (rounded_up % 4096)
316
317
Ying Wang7e6d4e42010-12-13 16:25:36 -0800318def CloseInheritedPipes():
319 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
320 before doing other work."""
321 if platform.system() != "Darwin":
322 return
323 for d in range(3, 1025):
324 try:
325 stat = os.fstat(d)
326 if stat is not None:
327 pipebit = stat[0] & 0x1000
328 if pipebit != 0:
329 os.close(d)
330 except OSError:
331 pass
332
333
Tao Bao1c320f82019-10-04 23:25:12 -0700334class BuildInfo(object):
335 """A class that holds the information for a given build.
336
337 This class wraps up the property querying for a given source or target build.
338 It abstracts away the logic of handling OEM-specific properties, and caches
339 the commonly used properties such as fingerprint.
340
341 There are two types of info dicts: a) build-time info dict, which is generated
342 at build time (i.e. included in a target_files zip); b) OEM info dict that is
343 specified at package generation time (via command line argument
344 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
345 having "oem_fingerprint_properties" in build-time info dict), all the queries
346 would be answered based on build-time info dict only. Otherwise if using
347 OEM-specific properties, some of them will be calculated from two info dicts.
348
349 Users can query properties similarly as using a dict() (e.g. info['fstab']),
Daniel Normand5fe8622020-01-08 17:01:11 -0800350 or to query build properties via GetBuildProp() or GetPartitionBuildProp().
Tao Bao1c320f82019-10-04 23:25:12 -0700351
352 Attributes:
353 info_dict: The build-time info dict.
354 is_ab: Whether it's a build that uses A/B OTA.
355 oem_dicts: A list of OEM dicts.
356 oem_props: A list of OEM properties that should be read from OEM dicts; None
357 if the build doesn't use any OEM-specific property.
358 fingerprint: The fingerprint of the build, which would be calculated based
359 on OEM properties if applicable.
360 device: The device name, which could come from OEM dicts if applicable.
361 """
362
363 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
364 "ro.product.manufacturer", "ro.product.model",
365 "ro.product.name"]
Steven Laver8e2086e2020-04-27 16:26:31 -0700366 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT = [
367 "product", "odm", "vendor", "system_ext", "system"]
368 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10 = [
369 "product", "product_services", "odm", "vendor", "system"]
370 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY = []
Tao Bao1c320f82019-10-04 23:25:12 -0700371
Tao Bao3ed35d32019-10-07 20:48:48 -0700372 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700373 """Initializes a BuildInfo instance with the given dicts.
374
375 Note that it only wraps up the given dicts, without making copies.
376
377 Arguments:
378 info_dict: The build-time info dict.
379 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
380 that it always uses the first dict to calculate the fingerprint or the
381 device name. The rest would be used for asserting OEM properties only
382 (e.g. one package can be installed on one of these devices).
383
384 Raises:
385 ValueError: On invalid inputs.
386 """
387 self.info_dict = info_dict
388 self.oem_dicts = oem_dicts
389
390 self._is_ab = info_dict.get("ab_update") == "true"
Tao Bao1c320f82019-10-04 23:25:12 -0700391
Hongguang Chend7c160f2020-05-03 21:24:26 -0700392 # Skip _oem_props if oem_dicts is None to use BuildInfo in
393 # sign_target_files_apks
394 if self.oem_dicts:
395 self._oem_props = info_dict.get("oem_fingerprint_properties")
396 else:
397 self._oem_props = None
Tao Bao1c320f82019-10-04 23:25:12 -0700398
Daniel Normand5fe8622020-01-08 17:01:11 -0800399 def check_fingerprint(fingerprint):
400 if (" " in fingerprint or any(ord(ch) > 127 for ch in fingerprint)):
401 raise ValueError(
402 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
403 "3.2.2. Build Parameters.".format(fingerprint))
404
Daniel Normand5fe8622020-01-08 17:01:11 -0800405 self._partition_fingerprints = {}
Yifan Hong5057b952021-01-07 14:09:57 -0800406 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800407 try:
408 fingerprint = self.CalculatePartitionFingerprint(partition)
409 check_fingerprint(fingerprint)
410 self._partition_fingerprints[partition] = fingerprint
411 except ExternalError:
412 continue
413 if "system" in self._partition_fingerprints:
Yifan Hong5057b952021-01-07 14:09:57 -0800414 # system_other is not included in PARTITIONS_WITH_BUILD_PROP, but does
Daniel Normand5fe8622020-01-08 17:01:11 -0800415 # need a fingerprint when creating the image.
416 self._partition_fingerprints[
417 "system_other"] = self._partition_fingerprints["system"]
418
Tao Bao1c320f82019-10-04 23:25:12 -0700419 # These two should be computed only after setting self._oem_props.
420 self._device = self.GetOemProperty("ro.product.device")
421 self._fingerprint = self.CalculateFingerprint()
Daniel Normand5fe8622020-01-08 17:01:11 -0800422 check_fingerprint(self._fingerprint)
Tao Bao1c320f82019-10-04 23:25:12 -0700423
424 @property
425 def is_ab(self):
426 return self._is_ab
427
428 @property
429 def device(self):
430 return self._device
431
432 @property
433 def fingerprint(self):
434 return self._fingerprint
435
436 @property
Tao Bao1c320f82019-10-04 23:25:12 -0700437 def oem_props(self):
438 return self._oem_props
439
440 def __getitem__(self, key):
441 return self.info_dict[key]
442
443 def __setitem__(self, key, value):
444 self.info_dict[key] = value
445
446 def get(self, key, default=None):
447 return self.info_dict.get(key, default)
448
449 def items(self):
450 return self.info_dict.items()
451
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000452 def _GetRawBuildProp(self, prop, partition):
453 prop_file = '{}.build.prop'.format(
454 partition) if partition else 'build.prop'
455 partition_props = self.info_dict.get(prop_file)
456 if not partition_props:
457 return None
458 return partition_props.GetProp(prop)
459
Daniel Normand5fe8622020-01-08 17:01:11 -0800460 def GetPartitionBuildProp(self, prop, partition):
461 """Returns the inquired build property for the provided partition."""
Yifan Hong10482a22021-01-07 14:38:41 -0800462
463 # Boot image uses ro.[product.]bootimage instead of boot.
Daniel Norman2d7989a2021-04-05 17:40:47 +0000464 prop_partition = "bootimage" if partition == "boot" else partition
Yifan Hong10482a22021-01-07 14:38:41 -0800465
Daniel Normand5fe8622020-01-08 17:01:11 -0800466 # If provided a partition for this property, only look within that
467 # partition's build.prop.
468 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
Yifan Hong10482a22021-01-07 14:38:41 -0800469 prop = prop.replace("ro.product", "ro.product.{}".format(prop_partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800470 else:
Yifan Hong10482a22021-01-07 14:38:41 -0800471 prop = prop.replace("ro.", "ro.{}.".format(prop_partition))
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000472
473 prop_val = self._GetRawBuildProp(prop, partition)
474 if prop_val is not None:
475 return prop_val
476 raise ExternalError("couldn't find %s in %s.build.prop" %
477 (prop, partition))
Daniel Normand5fe8622020-01-08 17:01:11 -0800478
Tao Bao1c320f82019-10-04 23:25:12 -0700479 def GetBuildProp(self, prop):
Daniel Normand5fe8622020-01-08 17:01:11 -0800480 """Returns the inquired build property from the standard build.prop file."""
Tao Bao1c320f82019-10-04 23:25:12 -0700481 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
482 return self._ResolveRoProductBuildProp(prop)
483
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000484 prop_val = self._GetRawBuildProp(prop, None)
485 if prop_val is not None:
486 return prop_val
487
488 raise ExternalError("couldn't find %s in build.prop" % (prop,))
Tao Bao1c320f82019-10-04 23:25:12 -0700489
490 def _ResolveRoProductBuildProp(self, prop):
491 """Resolves the inquired ro.product.* build property"""
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000492 prop_val = self._GetRawBuildProp(prop, None)
Tao Bao1c320f82019-10-04 23:25:12 -0700493 if prop_val:
494 return prop_val
495
Steven Laver8e2086e2020-04-27 16:26:31 -0700496 default_source_order = self._GetRoProductPropsDefaultSourceOrder()
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000497 source_order_val = self._GetRawBuildProp(
498 "ro.product.property_source_order", None)
Tao Bao1c320f82019-10-04 23:25:12 -0700499 if source_order_val:
500 source_order = source_order_val.split(",")
501 else:
Steven Laver8e2086e2020-04-27 16:26:31 -0700502 source_order = default_source_order
Tao Bao1c320f82019-10-04 23:25:12 -0700503
504 # Check that all sources in ro.product.property_source_order are valid
Steven Laver8e2086e2020-04-27 16:26:31 -0700505 if any([x not in default_source_order for x in source_order]):
Tao Bao1c320f82019-10-04 23:25:12 -0700506 raise ExternalError(
507 "Invalid ro.product.property_source_order '{}'".format(source_order))
508
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000509 for source_partition in source_order:
Tao Bao1c320f82019-10-04 23:25:12 -0700510 source_prop = prop.replace(
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000511 "ro.product", "ro.product.{}".format(source_partition), 1)
512 prop_val = self._GetRawBuildProp(source_prop, source_partition)
Tao Bao1c320f82019-10-04 23:25:12 -0700513 if prop_val:
514 return prop_val
515
516 raise ExternalError("couldn't resolve {}".format(prop))
517
Steven Laver8e2086e2020-04-27 16:26:31 -0700518 def _GetRoProductPropsDefaultSourceOrder(self):
519 # NOTE: refer to CDDs and android.os.Build.VERSION for the definition and
520 # values of these properties for each Android release.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000521 android_codename = self._GetRawBuildProp("ro.build.version.codename", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700522 if android_codename == "REL":
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000523 android_version = self._GetRawBuildProp("ro.build.version.release", None)
Steven Laver8e2086e2020-04-27 16:26:31 -0700524 if android_version == "10":
525 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_ANDROID_10
526 # NOTE: float() conversion of android_version will have rounding error.
527 # We are checking for "9" or less, and using "< 10" is well outside of
528 # possible floating point rounding.
529 try:
530 android_version_val = float(android_version)
531 except ValueError:
532 android_version_val = 0
533 if android_version_val < 10:
534 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_LEGACY
535 return BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER_CURRENT
536
Tianjieb37c5be2020-10-15 21:27:10 -0700537 def _GetPlatformVersion(self):
538 version_sdk = self.GetBuildProp("ro.build.version.sdk")
539 # init code switches to version_release_or_codename (see b/158483506). After
540 # API finalization, release_or_codename will be the same as release. This
541 # is the best effort to support pre-S dev stage builds.
542 if int(version_sdk) >= 30:
543 try:
544 return self.GetBuildProp("ro.build.version.release_or_codename")
545 except ExternalError:
546 logger.warning('Failed to find ro.build.version.release_or_codename')
547
548 return self.GetBuildProp("ro.build.version.release")
549
550 def _GetPartitionPlatformVersion(self, partition):
551 try:
552 return self.GetPartitionBuildProp("ro.build.version.release_or_codename",
553 partition)
554 except ExternalError:
555 return self.GetPartitionBuildProp("ro.build.version.release",
556 partition)
557
Tao Bao1c320f82019-10-04 23:25:12 -0700558 def GetOemProperty(self, key):
559 if self.oem_props is not None and key in self.oem_props:
560 return self.oem_dicts[0][key]
561 return self.GetBuildProp(key)
562
Daniel Normand5fe8622020-01-08 17:01:11 -0800563 def GetPartitionFingerprint(self, partition):
564 return self._partition_fingerprints.get(partition, None)
565
566 def CalculatePartitionFingerprint(self, partition):
567 try:
568 return self.GetPartitionBuildProp("ro.build.fingerprint", partition)
569 except ExternalError:
570 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
571 self.GetPartitionBuildProp("ro.product.brand", partition),
572 self.GetPartitionBuildProp("ro.product.name", partition),
573 self.GetPartitionBuildProp("ro.product.device", partition),
Tianjieb37c5be2020-10-15 21:27:10 -0700574 self._GetPartitionPlatformVersion(partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800575 self.GetPartitionBuildProp("ro.build.id", partition),
Kelvin Zhang0876c412020-06-23 15:06:58 -0400576 self.GetPartitionBuildProp(
577 "ro.build.version.incremental", partition),
Daniel Normand5fe8622020-01-08 17:01:11 -0800578 self.GetPartitionBuildProp("ro.build.type", partition),
579 self.GetPartitionBuildProp("ro.build.tags", partition))
580
Tao Bao1c320f82019-10-04 23:25:12 -0700581 def CalculateFingerprint(self):
582 if self.oem_props is None:
583 try:
584 return self.GetBuildProp("ro.build.fingerprint")
585 except ExternalError:
586 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
587 self.GetBuildProp("ro.product.brand"),
588 self.GetBuildProp("ro.product.name"),
589 self.GetBuildProp("ro.product.device"),
Tianjieb37c5be2020-10-15 21:27:10 -0700590 self._GetPlatformVersion(),
Tao Bao1c320f82019-10-04 23:25:12 -0700591 self.GetBuildProp("ro.build.id"),
592 self.GetBuildProp("ro.build.version.incremental"),
593 self.GetBuildProp("ro.build.type"),
594 self.GetBuildProp("ro.build.tags"))
595 return "%s/%s/%s:%s" % (
596 self.GetOemProperty("ro.product.brand"),
597 self.GetOemProperty("ro.product.name"),
598 self.GetOemProperty("ro.product.device"),
599 self.GetBuildProp("ro.build.thumbprint"))
600
601 def WriteMountOemScript(self, script):
602 assert self.oem_props is not None
603 recovery_mount_options = self.info_dict.get("recovery_mount_options")
604 script.Mount("/oem", recovery_mount_options)
605
606 def WriteDeviceAssertions(self, script, oem_no_mount):
607 # Read the property directly if not using OEM properties.
608 if not self.oem_props:
609 script.AssertDevice(self.device)
610 return
611
612 # Otherwise assert OEM properties.
613 if not self.oem_dicts:
614 raise ExternalError(
615 "No OEM file provided to answer expected assertions")
616
617 for prop in self.oem_props.split():
618 values = []
619 for oem_dict in self.oem_dicts:
620 if prop in oem_dict:
621 values.append(oem_dict[prop])
622 if not values:
623 raise ExternalError(
624 "The OEM file is missing the property %s" % (prop,))
625 script.AssertOemProperty(prop, values, oem_no_mount)
626
627
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000628def ReadFromInputFile(input_file, fn):
629 """Reads the contents of fn from input zipfile or directory."""
630 if isinstance(input_file, zipfile.ZipFile):
631 return input_file.read(fn).decode()
632 else:
633 path = os.path.join(input_file, *fn.split("/"))
634 try:
635 with open(path) as f:
636 return f.read()
637 except IOError as e:
638 if e.errno == errno.ENOENT:
639 raise KeyError(fn)
640
641
Yifan Hong10482a22021-01-07 14:38:41 -0800642def ExtractFromInputFile(input_file, fn):
643 """Extracts the contents of fn from input zipfile or directory into a file."""
644 if isinstance(input_file, zipfile.ZipFile):
645 tmp_file = MakeTempFile(os.path.basename(fn))
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500646 with open(tmp_file, 'wb') as f:
Yifan Hong10482a22021-01-07 14:38:41 -0800647 f.write(input_file.read(fn))
648 return tmp_file
649 else:
650 file = os.path.join(input_file, *fn.split("/"))
651 if not os.path.exists(file):
652 raise KeyError(fn)
653 return file
654
jiajia tangf3f842b2021-03-17 21:49:44 +0800655class RamdiskFormat(object):
656 LZ4 = 1
657 GZ = 2
Yifan Hong10482a22021-01-07 14:38:41 -0800658
jiajia tang836f76b2021-04-02 14:48:26 +0800659def _GetRamdiskFormat(info_dict):
660 if info_dict.get('lz4_ramdisks') == 'true':
661 ramdisk_format = RamdiskFormat.LZ4
662 else:
663 ramdisk_format = RamdiskFormat.GZ
664 return ramdisk_format
665
Tao Bao410ad8b2018-08-24 12:08:38 -0700666def LoadInfoDict(input_file, repacking=False):
667 """Loads the key/value pairs from the given input target_files.
668
Tianjiea85bdf02020-07-29 11:56:19 -0700669 It reads `META/misc_info.txt` file in the target_files input, does validation
Tao Bao410ad8b2018-08-24 12:08:38 -0700670 checks and returns the parsed key/value pairs for to the given build. It's
671 usually called early when working on input target_files files, e.g. when
672 generating OTAs, or signing builds. Note that the function may be called
673 against an old target_files file (i.e. from past dessert releases). So the
674 property parsing needs to be backward compatible.
675
676 In a `META/misc_info.txt`, a few properties are stored as links to the files
677 in the PRODUCT_OUT directory. It works fine with the build system. However,
678 they are no longer available when (re)generating images from target_files zip.
679 When `repacking` is True, redirect these properties to the actual files in the
680 unzipped directory.
681
682 Args:
683 input_file: The input target_files file, which could be an open
684 zipfile.ZipFile instance, or a str for the dir that contains the files
685 unzipped from a target_files file.
686 repacking: Whether it's trying repack an target_files file after loading the
687 info dict (default: False). If so, it will rewrite a few loaded
688 properties (e.g. selinux_fc, root_dir) to point to the actual files in
689 target_files file. When doing repacking, `input_file` must be a dir.
690
691 Returns:
692 A dict that contains the parsed key/value pairs.
693
694 Raises:
695 AssertionError: On invalid input arguments.
696 ValueError: On malformed input values.
697 """
698 if repacking:
699 assert isinstance(input_file, str), \
700 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700701
Doug Zongkerc9253822014-02-04 12:17:58 -0800702 def read_helper(fn):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000703 return ReadFromInputFile(input_file, fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800704
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700705 try:
Michael Runge6e836112014-04-15 17:40:21 -0700706 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700707 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700708 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700709
Tao Bao410ad8b2018-08-24 12:08:38 -0700710 if "recovery_api_version" not in d:
711 raise ValueError("Failed to find 'recovery_api_version'")
712 if "fstab_version" not in d:
713 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800714
Tao Bao410ad8b2018-08-24 12:08:38 -0700715 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700716 # "selinux_fc" properties should point to the file_contexts files
717 # (file_contexts.bin) under META/.
718 for key in d:
719 if key.endswith("selinux_fc"):
720 fc_basename = os.path.basename(d[key])
721 fc_config = os.path.join(input_file, "META", fc_basename)
722 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700723
Daniel Norman72c626f2019-05-13 15:58:14 -0700724 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700725
Tom Cherryd14b8952018-08-09 14:26:00 -0700726 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700727 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700728 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700729 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700730
David Anderson0ec64ac2019-12-06 12:21:18 -0800731 # Redirect {partition}_base_fs_file for each of the named partitions.
Yifan Hongcfb917a2020-05-07 14:58:20 -0700732 for part_name in ["system", "vendor", "system_ext", "product", "odm",
Yifan Hongf496f1b2020-07-15 16:52:59 -0700733 "vendor_dlkm", "odm_dlkm"]:
David Anderson0ec64ac2019-12-06 12:21:18 -0800734 key_name = part_name + "_base_fs_file"
735 if key_name not in d:
736 continue
737 basename = os.path.basename(d[key_name])
738 base_fs_file = os.path.join(input_file, "META", basename)
739 if os.path.exists(base_fs_file):
740 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700741 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700742 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800743 "Failed to find %s base fs file: %s", part_name, base_fs_file)
744 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700745
Doug Zongker37974732010-09-16 17:44:38 -0700746 def makeint(key):
747 if key in d:
748 d[key] = int(d[key], 0)
749
750 makeint("recovery_api_version")
751 makeint("blocksize")
752 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700753 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700754 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700755 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700756 makeint("recovery_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800757 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700758
Steve Muckle903a1ca2020-05-07 17:32:10 -0700759 boot_images = "boot.img"
760 if "boot_images" in d:
761 boot_images = d["boot_images"]
762 for b in boot_images.split():
Kelvin Zhang0876c412020-06-23 15:06:58 -0400763 makeint(b.replace(".img", "_size"))
Steve Muckle903a1ca2020-05-07 17:32:10 -0700764
Tao Bao765668f2019-10-04 22:03:00 -0700765 # Load recovery fstab if applicable.
766 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
jiajia tang836f76b2021-04-02 14:48:26 +0800767 ramdisk_format = _GetRamdiskFormat(d)
Tianjie Xucfa86222016-03-07 16:31:19 -0800768
Tianjie Xu861f4132018-09-12 11:49:33 -0700769 # Tries to load the build props for all partitions with care_map, including
770 # system and vendor.
Yifan Hong5057b952021-01-07 14:09:57 -0800771 for partition in PARTITIONS_WITH_BUILD_PROP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800772 partition_prop = "{}.build.prop".format(partition)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000773 d[partition_prop] = PartitionBuildProps.FromInputFile(
jiajia tangf3f842b2021-03-17 21:49:44 +0800774 input_file, partition, ramdisk_format=ramdisk_format)
Tianjie Xu861f4132018-09-12 11:49:33 -0700775 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800776
Tao Bao3ed35d32019-10-07 20:48:48 -0700777 # Set up the salt (based on fingerprint) that will be used when adding AVB
778 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800779 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700780 build_info = BuildInfo(d)
Yifan Hong5057b952021-01-07 14:09:57 -0800781 for partition in PARTITIONS_WITH_BUILD_PROP:
Daniel Normand5fe8622020-01-08 17:01:11 -0800782 fingerprint = build_info.GetPartitionFingerprint(partition)
783 if fingerprint:
Daniel Norman2d7989a2021-04-05 17:40:47 +0000784 d["avb_{}_salt".format(partition)] = sha256(fingerprint.encode()).hexdigest()
Kelvin Zhang39aea442020-08-17 11:04:25 -0400785 try:
786 d["ab_partitions"] = read_helper("META/ab_partitions.txt").split("\n")
787 except KeyError:
788 logger.warning("Can't find META/ab_partitions.txt")
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700789 return d
790
Tao Baod1de6f32017-03-01 16:38:48 -0800791
Daniel Norman2d7989a2021-04-05 17:40:47 +0000792
Daniel Norman4cc9df62019-07-18 10:11:07 -0700793def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900794 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700795 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900796
Daniel Norman4cc9df62019-07-18 10:11:07 -0700797
798def LoadDictionaryFromFile(file_path):
799 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900800 return LoadDictionaryFromLines(lines)
801
802
Michael Runge6e836112014-04-15 17:40:21 -0700803def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700804 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700805 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700806 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700807 if not line or line.startswith("#"):
808 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700809 if "=" in line:
810 name, value = line.split("=", 1)
811 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700812 return d
813
Tao Baod1de6f32017-03-01 16:38:48 -0800814
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000815class PartitionBuildProps(object):
816 """The class holds the build prop of a particular partition.
817
818 This class loads the build.prop and holds the build properties for a given
819 partition. It also partially recognizes the 'import' statement in the
820 build.prop; and calculates alternative values of some specific build
821 properties during runtime.
822
823 Attributes:
824 input_file: a zipped target-file or an unzipped target-file directory.
825 partition: name of the partition.
826 props_allow_override: a list of build properties to search for the
827 alternative values during runtime.
Tianjie Xu9afb2212020-05-10 21:48:15 +0000828 build_props: a dict of build properties for the given partition.
829 prop_overrides: a set of props that are overridden by import.
830 placeholder_values: A dict of runtime variables' values to replace the
831 placeholders in the build.prop file. We expect exactly one value for
832 each of the variables.
jiajia tangf3f842b2021-03-17 21:49:44 +0800833 ramdisk_format: If name is "boot", the format of ramdisk inside the
834 boot image. Otherwise, its value is ignored.
835 Use lz4 to decompress by default. If its value is gzip, use minigzip.
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000836 """
Kelvin Zhang0876c412020-06-23 15:06:58 -0400837
Tianjie Xu9afb2212020-05-10 21:48:15 +0000838 def __init__(self, input_file, name, placeholder_values=None):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000839 self.input_file = input_file
840 self.partition = name
841 self.props_allow_override = [props.format(name) for props in [
Tianjie Xu9afb2212020-05-10 21:48:15 +0000842 'ro.product.{}.brand', 'ro.product.{}.name', 'ro.product.{}.device']]
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000843 self.build_props = {}
Tianjie Xu9afb2212020-05-10 21:48:15 +0000844 self.prop_overrides = set()
845 self.placeholder_values = {}
846 if placeholder_values:
847 self.placeholder_values = copy.deepcopy(placeholder_values)
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000848
849 @staticmethod
850 def FromDictionary(name, build_props):
851 """Constructs an instance from a build prop dictionary."""
852
853 props = PartitionBuildProps("unknown", name)
854 props.build_props = build_props.copy()
855 return props
856
857 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800858 def FromInputFile(input_file, name, placeholder_values=None, ramdisk_format=RamdiskFormat.LZ4):
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000859 """Loads the build.prop file and builds the attributes."""
Yifan Hong10482a22021-01-07 14:38:41 -0800860
861 if name == "boot":
jiajia tangf3f842b2021-03-17 21:49:44 +0800862 data = PartitionBuildProps._ReadBootPropFile(input_file, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800863 else:
864 data = PartitionBuildProps._ReadPartitionPropFile(input_file, name)
865
866 props = PartitionBuildProps(input_file, name, placeholder_values)
867 props._LoadBuildProp(data)
868 return props
869
870 @staticmethod
jiajia tangf3f842b2021-03-17 21:49:44 +0800871 def _ReadBootPropFile(input_file, ramdisk_format):
Yifan Hong10482a22021-01-07 14:38:41 -0800872 """
873 Read build.prop for boot image from input_file.
874 Return empty string if not found.
875 """
876 try:
877 boot_img = ExtractFromInputFile(input_file, 'IMAGES/boot.img')
878 except KeyError:
879 logger.warning('Failed to read IMAGES/boot.img')
880 return ''
jiajia tangf3f842b2021-03-17 21:49:44 +0800881 prop_file = GetBootImageBuildProp(boot_img, ramdisk_format=ramdisk_format)
Yifan Hong10482a22021-01-07 14:38:41 -0800882 if prop_file is None:
883 return ''
Kelvin Zhang645dcb82021-02-09 17:52:50 -0500884 with open(prop_file, "r") as f:
885 return f.read()
Yifan Hong10482a22021-01-07 14:38:41 -0800886
887 @staticmethod
888 def _ReadPartitionPropFile(input_file, name):
889 """
890 Read build.prop for name from input_file.
891 Return empty string if not found.
892 """
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000893 data = ''
894 for prop_file in ['{}/etc/build.prop'.format(name.upper()),
895 '{}/build.prop'.format(name.upper())]:
896 try:
897 data = ReadFromInputFile(input_file, prop_file)
898 break
899 except KeyError:
900 logger.warning('Failed to read %s', prop_file)
Yifan Hong10482a22021-01-07 14:38:41 -0800901 return data
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000902
Yifan Hong125d0b62020-09-24 17:07:03 -0700903 @staticmethod
904 def FromBuildPropFile(name, build_prop_file):
905 """Constructs an instance from a build prop file."""
906
907 props = PartitionBuildProps("unknown", name)
908 with open(build_prop_file) as f:
909 props._LoadBuildProp(f.read())
910 return props
911
Tianjie Xu9afb2212020-05-10 21:48:15 +0000912 def _LoadBuildProp(self, data):
913 for line in data.split('\n'):
914 line = line.strip()
915 if not line or line.startswith("#"):
916 continue
917 if line.startswith("import"):
918 overrides = self._ImportParser(line)
919 duplicates = self.prop_overrides.intersection(overrides.keys())
920 if duplicates:
921 raise ValueError('prop {} is overridden multiple times'.format(
922 ','.join(duplicates)))
923 self.prop_overrides = self.prop_overrides.union(overrides.keys())
924 self.build_props.update(overrides)
925 elif "=" in line:
926 name, value = line.split("=", 1)
927 if name in self.prop_overrides:
928 raise ValueError('prop {} is set again after overridden by import '
929 'statement'.format(name))
930 self.build_props[name] = value
931
932 def _ImportParser(self, line):
933 """Parses the build prop in a given import statement."""
934
935 tokens = line.split()
Kelvin Zhang0876c412020-06-23 15:06:58 -0400936 if tokens[0] != 'import' or (len(tokens) != 2 and len(tokens) != 3):
Tianjie Xu9afb2212020-05-10 21:48:15 +0000937 raise ValueError('Unrecognized import statement {}'.format(line))
Hongguang Chenb4702b72020-05-13 18:05:20 -0700938
939 if len(tokens) == 3:
940 logger.info("Import %s from %s, skip", tokens[2], tokens[1])
941 return {}
942
Tianjie Xu9afb2212020-05-10 21:48:15 +0000943 import_path = tokens[1]
944 if not re.match(r'^/{}/.*\.prop$'.format(self.partition), import_path):
945 raise ValueError('Unrecognized import path {}'.format(line))
946
947 # We only recognize a subset of import statement that the init process
948 # supports. And we can loose the restriction based on how the dynamic
949 # fingerprint is used in practice. The placeholder format should be
950 # ${placeholder}, and its value should be provided by the caller through
951 # the placeholder_values.
952 for prop, value in self.placeholder_values.items():
953 prop_place_holder = '${{{}}}'.format(prop)
954 if prop_place_holder in import_path:
955 import_path = import_path.replace(prop_place_holder, value)
956 if '$' in import_path:
957 logger.info('Unresolved place holder in import path %s', import_path)
958 return {}
959
960 import_path = import_path.replace('/{}'.format(self.partition),
961 self.partition.upper())
962 logger.info('Parsing build props override from %s', import_path)
963
964 lines = ReadFromInputFile(self.input_file, import_path).split('\n')
965 d = LoadDictionaryFromLines(lines)
966 return {key: val for key, val in d.items()
967 if key in self.props_allow_override}
968
Tianjie Xu0fde41e2020-05-09 05:24:18 +0000969 def GetProp(self, prop):
970 return self.build_props.get(prop)
971
972
Tianjie Xucfa86222016-03-07 16:31:19 -0800973def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
974 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700975 class Partition(object):
Yifan Hong65afc072020-04-17 10:08:10 -0700976 def __init__(self, mount_point, fs_type, device, length, context, slotselect):
Dan Albert8b72aef2015-03-23 19:13:21 -0700977 self.mount_point = mount_point
978 self.fs_type = fs_type
979 self.device = device
980 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700981 self.context = context
Yifan Hong65afc072020-04-17 10:08:10 -0700982 self.slotselect = slotselect
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700983
984 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800985 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700986 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700987 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700988 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700989
Tao Baod1de6f32017-03-01 16:38:48 -0800990 assert fstab_version == 2
991
992 d = {}
993 for line in data.split("\n"):
994 line = line.strip()
995 if not line or line.startswith("#"):
996 continue
997
998 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
999 pieces = line.split()
1000 if len(pieces) != 5:
1001 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
1002
1003 # Ignore entries that are managed by vold.
1004 options = pieces[4]
1005 if "voldmanaged=" in options:
1006 continue
1007
1008 # It's a good line, parse it.
1009 length = 0
Yifan Hong65afc072020-04-17 10:08:10 -07001010 slotselect = False
Tao Baod1de6f32017-03-01 16:38:48 -08001011 options = options.split(",")
1012 for i in options:
1013 if i.startswith("length="):
1014 length = int(i[7:])
Yifan Hong65afc072020-04-17 10:08:10 -07001015 elif i == "slotselect":
1016 slotselect = True
Doug Zongker086cbb02011-02-17 15:54:20 -08001017 else:
Tao Baod1de6f32017-03-01 16:38:48 -08001018 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -07001019 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001020
Tao Baod1de6f32017-03-01 16:38:48 -08001021 mount_flags = pieces[3]
1022 # Honor the SELinux context if present.
1023 context = None
1024 for i in mount_flags.split(","):
1025 if i.startswith("context="):
1026 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -08001027
Tao Baod1de6f32017-03-01 16:38:48 -08001028 mount_point = pieces[1]
1029 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
Yifan Hong65afc072020-04-17 10:08:10 -07001030 device=pieces[0], length=length, context=context,
1031 slotselect=slotselect)
Ken Sumrall3b07cf12013-02-19 17:35:29 -08001032
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001033 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001034 # system. Other areas assume system is always at "/system" so point /system
1035 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001036 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -08001037 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -07001038 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001039 return d
1040
1041
Tao Bao765668f2019-10-04 22:03:00 -07001042def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
1043 """Finds the path to recovery fstab and loads its contents."""
1044 # recovery fstab is only meaningful when installing an update via recovery
1045 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
Yifan Hong65afc072020-04-17 10:08:10 -07001046 if info_dict.get('ab_update') == 'true' and \
1047 info_dict.get("allow_non_ab") != "true":
Tao Bao765668f2019-10-04 22:03:00 -07001048 return None
1049
1050 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
1051 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
1052 # cases, since it may load the info_dict from an old build (e.g. when
1053 # generating incremental OTAs from that build).
1054 system_root_image = info_dict.get('system_root_image') == 'true'
1055 if info_dict.get('no_recovery') != 'true':
1056 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
1057 if isinstance(input_file, zipfile.ZipFile):
1058 if recovery_fstab_path not in input_file.namelist():
1059 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1060 else:
1061 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1062 if not os.path.exists(path):
1063 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
1064 return LoadRecoveryFSTab(
1065 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1066 system_root_image)
1067
1068 if info_dict.get('recovery_as_boot') == 'true':
1069 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
1070 if isinstance(input_file, zipfile.ZipFile):
1071 if recovery_fstab_path not in input_file.namelist():
1072 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1073 else:
1074 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
1075 if not os.path.exists(path):
1076 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
1077 return LoadRecoveryFSTab(
1078 read_helper, info_dict['fstab_version'], recovery_fstab_path,
1079 system_root_image)
1080
1081 return None
1082
1083
Doug Zongker37974732010-09-16 17:44:38 -07001084def DumpInfoDict(d):
1085 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -07001086 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -07001087
Dan Albert8b72aef2015-03-23 19:13:21 -07001088
Daniel Norman55417142019-11-25 16:04:36 -08001089def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001090 """Merges dynamic partition info variables.
1091
1092 Args:
1093 framework_dict: The dictionary of dynamic partition info variables from the
1094 partial framework target files.
1095 vendor_dict: The dictionary of dynamic partition info variables from the
1096 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001097
1098 Returns:
1099 The merged dynamic partition info dictionary.
1100 """
Daniel Normanb0c75912020-09-24 14:30:21 -07001101
1102 def uniq_concat(a, b):
1103 combined = set(a.split(" "))
1104 combined.update(set(b.split(" ")))
1105 combined = [item.strip() for item in combined if item.strip()]
1106 return " ".join(sorted(combined))
1107
1108 if (framework_dict.get("use_dynamic_partitions") !=
Daniel Norman2d7989a2021-04-05 17:40:47 +00001109 "true") or (vendor_dict.get("use_dynamic_partitions") != "true"):
Daniel Normanb0c75912020-09-24 14:30:21 -07001110 raise ValueError("Both dictionaries must have use_dynamic_partitions=true")
1111
1112 merged_dict = {"use_dynamic_partitions": "true"}
1113
1114 merged_dict["dynamic_partition_list"] = uniq_concat(
1115 framework_dict.get("dynamic_partition_list", ""),
1116 vendor_dict.get("dynamic_partition_list", ""))
1117
1118 # Super block devices are defined by the vendor dict.
1119 if "super_block_devices" in vendor_dict:
1120 merged_dict["super_block_devices"] = vendor_dict["super_block_devices"]
1121 for block_device in merged_dict["super_block_devices"].split(" "):
1122 key = "super_%s_device_size" % block_device
1123 if key not in vendor_dict:
1124 raise ValueError("Vendor dict does not contain required key %s." % key)
1125 merged_dict[key] = vendor_dict[key]
1126
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001127 # Partition groups and group sizes are defined by the vendor dict because
1128 # these values may vary for each board that uses a shared system image.
1129 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001130 for partition_group in merged_dict["super_partition_groups"].split(" "):
1131 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -08001132 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001133 if key not in vendor_dict:
1134 raise ValueError("Vendor dict does not contain required key %s." % key)
1135 merged_dict[key] = vendor_dict[key]
1136
1137 # Set the partition group's partition list using a concatenation of the
1138 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -08001139 key = "super_%s_partition_list" % partition_group
Daniel Normanb0c75912020-09-24 14:30:21 -07001140 merged_dict[key] = uniq_concat(
1141 framework_dict.get(key, ""), vendor_dict.get(key, ""))
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +05301142
Daniel Normanb0c75912020-09-24 14:30:21 -07001143 # Various other flags should be copied from the vendor dict, if defined.
1144 for key in ("virtual_ab", "virtual_ab_retrofit", "lpmake",
1145 "super_metadata_device", "super_partition_error_limit",
1146 "super_partition_size"):
1147 if key in vendor_dict.keys():
1148 merged_dict[key] = vendor_dict[key]
1149
Daniel Normanbfc51ef2019-07-24 14:34:54 -07001150 return merged_dict
1151
1152
Daniel Norman21c34f72020-11-11 17:25:50 -08001153def PartitionMapFromTargetFiles(target_files_dir):
1154 """Builds a map from partition -> path within an extracted target files directory."""
1155 # Keep possible_subdirs in sync with build/make/core/board_config.mk.
1156 possible_subdirs = {
1157 "system": ["SYSTEM"],
1158 "vendor": ["VENDOR", "SYSTEM/vendor"],
1159 "product": ["PRODUCT", "SYSTEM/product"],
1160 "system_ext": ["SYSTEM_EXT", "SYSTEM/system_ext"],
1161 "odm": ["ODM", "VENDOR/odm", "SYSTEM/vendor/odm"],
1162 "vendor_dlkm": [
1163 "VENDOR_DLKM", "VENDOR/vendor_dlkm", "SYSTEM/vendor/vendor_dlkm"
1164 ],
1165 "odm_dlkm": ["ODM_DLKM", "VENDOR/odm_dlkm", "SYSTEM/vendor/odm_dlkm"],
1166 }
1167 partition_map = {}
1168 for partition, subdirs in possible_subdirs.items():
1169 for subdir in subdirs:
1170 if os.path.exists(os.path.join(target_files_dir, subdir)):
1171 partition_map[partition] = subdir
1172 break
1173 return partition_map
1174
1175
Daniel Normand3351562020-10-29 12:33:11 -07001176def SharedUidPartitionViolations(uid_dict, partition_groups):
1177 """Checks for APK sharedUserIds that cross partition group boundaries.
1178
1179 This uses a single or merged build's shareduid_violation_modules.json
1180 output file, as generated by find_shareduid_violation.py or
1181 core/tasks/find-shareduid-violation.mk.
1182
1183 An error is defined as a sharedUserId that is found in a set of partitions
1184 that span more than one partition group.
1185
1186 Args:
1187 uid_dict: A dictionary created by using the standard json module to read a
1188 complete shareduid_violation_modules.json file.
1189 partition_groups: A list of groups, where each group is a list of
1190 partitions.
1191
1192 Returns:
1193 A list of error messages.
1194 """
1195 errors = []
1196 for uid, partitions in uid_dict.items():
1197 found_in_groups = [
1198 group for group in partition_groups
1199 if set(partitions.keys()) & set(group)
1200 ]
1201 if len(found_in_groups) > 1:
1202 errors.append(
1203 "APK sharedUserId \"%s\" found across partition groups in partitions \"%s\""
1204 % (uid, ",".join(sorted(partitions.keys()))))
1205 return errors
1206
1207
Daniel Norman21c34f72020-11-11 17:25:50 -08001208def RunHostInitVerifier(product_out, partition_map):
1209 """Runs host_init_verifier on the init rc files within partitions.
1210
1211 host_init_verifier searches the etc/init path within each partition.
1212
1213 Args:
1214 product_out: PRODUCT_OUT directory, containing partition directories.
1215 partition_map: A map of partition name -> relative path within product_out.
1216 """
1217 allowed_partitions = ("system", "system_ext", "product", "vendor", "odm")
1218 cmd = ["host_init_verifier"]
1219 for partition, path in partition_map.items():
1220 if partition not in allowed_partitions:
1221 raise ExternalError("Unable to call host_init_verifier for partition %s" %
1222 partition)
1223 cmd.extend(["--out_%s" % partition, os.path.join(product_out, path)])
1224 # Add --property-contexts if the file exists on the partition.
1225 property_contexts = "%s_property_contexts" % (
1226 "plat" if partition == "system" else partition)
1227 property_contexts_path = os.path.join(product_out, path, "etc", "selinux",
1228 property_contexts)
1229 if os.path.exists(property_contexts_path):
1230 cmd.append("--property-contexts=%s" % property_contexts_path)
1231 # Add the passwd file if the file exists on the partition.
1232 passwd_path = os.path.join(product_out, path, "etc", "passwd")
1233 if os.path.exists(passwd_path):
1234 cmd.extend(["-p", passwd_path])
1235 return RunAndCheckOutput(cmd)
1236
1237
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001238def AppendAVBSigningArgs(cmd, partition):
1239 """Append signing arguments for avbtool."""
1240 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
1241 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -07001242 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
1243 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1244 if os.path.exists(new_key_path):
1245 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001246 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
1247 if key_path and algorithm:
1248 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -07001249 avb_salt = OPTIONS.info_dict.get("avb_salt")
1250 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -07001251 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -07001252 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001253
1254
Tao Bao765668f2019-10-04 22:03:00 -07001255def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -07001256 """Returns the VBMeta arguments for partition.
1257
1258 It sets up the VBMeta argument by including the partition descriptor from the
1259 given 'image', or by configuring the partition as a chained partition.
1260
1261 Args:
1262 partition: The name of the partition (e.g. "system").
1263 image: The path to the partition image.
1264 info_dict: A dict returned by common.LoadInfoDict(). Will use
1265 OPTIONS.info_dict if None has been given.
1266
1267 Returns:
1268 A list of VBMeta arguments.
1269 """
1270 if info_dict is None:
1271 info_dict = OPTIONS.info_dict
1272
1273 # Check if chain partition is used.
1274 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +08001275 if not key_path:
1276 return ["--include_descriptors_from_image", image]
1277
1278 # For a non-A/B device, we don't chain /recovery nor include its descriptor
1279 # into vbmeta.img. The recovery image will be configured on an independent
1280 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
1281 # See details at
1282 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -07001283 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +08001284 return []
1285
1286 # Otherwise chain the partition into vbmeta.
1287 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
1288 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -07001289
1290
Tao Bao02a08592018-07-22 12:40:45 -07001291def GetAvbChainedPartitionArg(partition, info_dict, key=None):
1292 """Constructs and returns the arg to build or verify a chained partition.
1293
1294 Args:
1295 partition: The partition name.
1296 info_dict: The info dict to look up the key info and rollback index
1297 location.
1298 key: The key to be used for building or verifying the partition. Defaults to
1299 the key listed in info_dict.
1300
1301 Returns:
1302 A string of form "partition:rollback_index_location:key" that can be used to
1303 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -07001304 """
1305 if key is None:
1306 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -07001307 if key and not os.path.exists(key) and OPTIONS.search_path:
1308 new_key_path = os.path.join(OPTIONS.search_path, key)
1309 if os.path.exists(new_key_path):
1310 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -07001311 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -07001312 rollback_index_location = info_dict[
1313 "avb_" + partition + "_rollback_index_location"]
1314 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
1315
1316
Tianjie20dd8f22020-04-19 15:51:16 -07001317def ConstructAftlMakeImageCommands(output_image):
1318 """Constructs the command to append the aftl image to vbmeta."""
Tianjie Xueaed60c2020-03-12 00:33:28 -07001319
1320 # Ensure the other AFTL parameters are set as well.
Tianjie0f307452020-04-01 12:20:21 -07001321 assert OPTIONS.aftl_tool_path is not None, 'No aftl tool provided.'
Tianjie Xueaed60c2020-03-12 00:33:28 -07001322 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
1323 assert OPTIONS.aftl_manufacturer_key_path is not None, \
1324 'No AFTL manufacturer key provided.'
1325
1326 vbmeta_image = MakeTempFile()
1327 os.rename(output_image, vbmeta_image)
1328 build_info = BuildInfo(OPTIONS.info_dict)
1329 version_incremental = build_info.GetBuildProp("ro.build.version.incremental")
Tianjie0f307452020-04-01 12:20:21 -07001330 aftltool = OPTIONS.aftl_tool_path
Tianjie20dd8f22020-04-19 15:51:16 -07001331 server_argument_list = [OPTIONS.aftl_server, OPTIONS.aftl_key_path]
Tianjie0f307452020-04-01 12:20:21 -07001332 aftl_cmd = [aftltool, "make_icp_from_vbmeta",
Tianjie Xueaed60c2020-03-12 00:33:28 -07001333 "--vbmeta_image_path", vbmeta_image,
1334 "--output", output_image,
1335 "--version_incremental", version_incremental,
Tianjie20dd8f22020-04-19 15:51:16 -07001336 "--transparency_log_servers", ','.join(server_argument_list),
Tianjie Xueaed60c2020-03-12 00:33:28 -07001337 "--manufacturer_key", OPTIONS.aftl_manufacturer_key_path,
1338 "--algorithm", "SHA256_RSA4096",
1339 "--padding", "4096"]
1340 if OPTIONS.aftl_signer_helper:
1341 aftl_cmd.extend(shlex.split(OPTIONS.aftl_signer_helper))
Tianjie20dd8f22020-04-19 15:51:16 -07001342 return aftl_cmd
1343
1344
1345def AddAftlInclusionProof(output_image):
1346 """Appends the aftl inclusion proof to the vbmeta image."""
1347
1348 aftl_cmd = ConstructAftlMakeImageCommands(output_image)
Tianjie Xueaed60c2020-03-12 00:33:28 -07001349 RunAndCheckOutput(aftl_cmd)
1350
1351 verify_cmd = ['aftltool', 'verify_image_icp', '--vbmeta_image_path',
1352 output_image, '--transparency_log_pub_keys',
1353 OPTIONS.aftl_key_path]
1354 RunAndCheckOutput(verify_cmd)
1355
1356
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001357def AppendGkiSigningArgs(cmd):
1358 """Append GKI signing arguments for mkbootimg."""
1359 # e.g., --gki_signing_key path/to/signing_key
1360 # --gki_signing_algorithm SHA256_RSA4096"
1361
1362 key_path = OPTIONS.info_dict.get("gki_signing_key_path")
1363 # It's fine that a non-GKI boot.img has no gki_signing_key_path.
1364 if not key_path:
1365 return
1366
1367 if not os.path.exists(key_path) and OPTIONS.search_path:
1368 new_key_path = os.path.join(OPTIONS.search_path, key_path)
1369 if os.path.exists(new_key_path):
1370 key_path = new_key_path
1371
1372 # Checks key_path exists, before appending --gki_signing_* args.
1373 if not os.path.exists(key_path):
1374 raise ExternalError('gki_signing_key_path: "{}" not found'.format(key_path))
1375
1376 algorithm = OPTIONS.info_dict.get("gki_signing_algorithm")
1377 if key_path and algorithm:
1378 cmd.extend(["--gki_signing_key", key_path,
1379 "--gki_signing_algorithm", algorithm])
1380
1381 signature_args = OPTIONS.info_dict.get("gki_signing_signature_args")
1382 if signature_args:
1383 cmd.extend(["--gki_signing_signature_args", signature_args])
1384
1385
Daniel Norman276f0622019-07-26 14:13:51 -07001386def BuildVBMeta(image_path, partitions, name, needed_partitions):
1387 """Creates a VBMeta image.
1388
1389 It generates the requested VBMeta image. The requested image could be for
1390 top-level or chained VBMeta image, which is determined based on the name.
1391
1392 Args:
1393 image_path: The output path for the new VBMeta image.
1394 partitions: A dict that's keyed by partition names with image paths as
Hongguang Chenf23364d2020-04-27 18:36:36 -07001395 values. Only valid partition names are accepted, as partitions listed
1396 in common.AVB_PARTITIONS and custom partitions listed in
1397 OPTIONS.info_dict.get("avb_custom_images_partition_list")
Daniel Norman276f0622019-07-26 14:13:51 -07001398 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
1399 needed_partitions: Partitions whose descriptors should be included into the
1400 generated VBMeta image.
1401
1402 Raises:
1403 AssertionError: On invalid input args.
1404 """
1405 avbtool = OPTIONS.info_dict["avb_avbtool"]
1406 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
1407 AppendAVBSigningArgs(cmd, name)
1408
Hongguang Chenf23364d2020-04-27 18:36:36 -07001409 custom_partitions = OPTIONS.info_dict.get(
1410 "avb_custom_images_partition_list", "").strip().split()
1411
Daniel Norman276f0622019-07-26 14:13:51 -07001412 for partition, path in partitions.items():
1413 if partition not in needed_partitions:
1414 continue
1415 assert (partition in AVB_PARTITIONS or
Hongguang Chenf23364d2020-04-27 18:36:36 -07001416 partition in AVB_VBMETA_PARTITIONS or
1417 partition in custom_partitions), \
Daniel Norman276f0622019-07-26 14:13:51 -07001418 'Unknown partition: {}'.format(partition)
1419 assert os.path.exists(path), \
1420 'Failed to find {} for {}'.format(path, partition)
1421 cmd.extend(GetAvbPartitionArg(partition, path))
1422
1423 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
1424 if args and args.strip():
1425 split_args = shlex.split(args)
1426 for index, arg in enumerate(split_args[:-1]):
Ivan Lozanob021b2a2020-07-28 09:31:06 -04001427 # Check that the image file exists. Some images might be defined
Daniel Norman276f0622019-07-26 14:13:51 -07001428 # as a path relative to source tree, which may not be available at the
1429 # same location when running this script (we have the input target_files
1430 # zip only). For such cases, we additionally scan other locations (e.g.
1431 # IMAGES/, RADIO/, etc) before bailing out.
1432 if arg == '--include_descriptors_from_image':
Tianjie Xueaed60c2020-03-12 00:33:28 -07001433 chained_image = split_args[index + 1]
1434 if os.path.exists(chained_image):
Daniel Norman276f0622019-07-26 14:13:51 -07001435 continue
1436 found = False
1437 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
1438 alt_path = os.path.join(
Tianjie Xueaed60c2020-03-12 00:33:28 -07001439 OPTIONS.input_tmp, dir_name, os.path.basename(chained_image))
Daniel Norman276f0622019-07-26 14:13:51 -07001440 if os.path.exists(alt_path):
1441 split_args[index + 1] = alt_path
1442 found = True
1443 break
Tianjie Xueaed60c2020-03-12 00:33:28 -07001444 assert found, 'Failed to find {}'.format(chained_image)
Daniel Norman276f0622019-07-26 14:13:51 -07001445 cmd.extend(split_args)
1446
1447 RunAndCheckOutput(cmd)
1448
Tianjie Xueaed60c2020-03-12 00:33:28 -07001449 # Generate the AFTL inclusion proof.
Dan Austin52903642019-12-12 15:44:00 -08001450 if OPTIONS.aftl_server is not None:
Tianjie Xueaed60c2020-03-12 00:33:28 -07001451 AddAftlInclusionProof(image_path)
1452
Daniel Norman276f0622019-07-26 14:13:51 -07001453
jiajia tang836f76b2021-04-02 14:48:26 +08001454def _MakeRamdisk(sourcedir, fs_config_file=None,
1455 ramdisk_format=RamdiskFormat.GZ):
Steve Mucklee1b10862019-07-10 10:49:37 -07001456 ramdisk_img = tempfile.NamedTemporaryFile()
1457
1458 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
1459 cmd = ["mkbootfs", "-f", fs_config_file,
1460 os.path.join(sourcedir, "RAMDISK")]
1461 else:
1462 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
1463 p1 = Run(cmd, stdout=subprocess.PIPE)
jiajia tang836f76b2021-04-02 14:48:26 +08001464 if ramdisk_format == RamdiskFormat.LZ4:
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001465 p2 = Run(["lz4", "-l", "-12", "--favor-decSpeed"], stdin=p1.stdout,
J. Avila98cd4cc2020-06-10 20:09:10 +00001466 stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001467 elif ramdisk_format == RamdiskFormat.GZ:
J. Avila98cd4cc2020-06-10 20:09:10 +00001468 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
jiajia tang836f76b2021-04-02 14:48:26 +08001469 else:
1470 raise ValueError("Only support lz4 or minigzip ramdisk format.")
Steve Mucklee1b10862019-07-10 10:49:37 -07001471
1472 p2.wait()
1473 p1.wait()
1474 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
J. Avila98cd4cc2020-06-10 20:09:10 +00001475 assert p2.returncode == 0, "compression of %s ramdisk failed" % (sourcedir,)
Steve Mucklee1b10862019-07-10 10:49:37 -07001476
1477 return ramdisk_img
1478
1479
Steve Muckle9793cf62020-04-08 18:27:00 -07001480def _BuildBootableImage(image_name, sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -08001481 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001482 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001483
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001484 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001485 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1486 we are building a two-step special image (i.e. building a recovery image to
1487 be loaded into /boot in two-step OTAs).
1488
1489 Return the image data, or None if sourcedir does not appear to contains files
1490 for building the requested image.
1491 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001492
Yifan Hong63c5ca12020-10-08 11:54:02 -07001493 if info_dict is None:
1494 info_dict = OPTIONS.info_dict
1495
Steve Muckle9793cf62020-04-08 18:27:00 -07001496 # "boot" or "recovery", without extension.
1497 partition_name = os.path.basename(sourcedir).lower()
1498
Yifan Hong63c5ca12020-10-08 11:54:02 -07001499 kernel = None
Steve Muckle9793cf62020-04-08 18:27:00 -07001500 if partition_name == "recovery":
Yifan Hong63c5ca12020-10-08 11:54:02 -07001501 if info_dict.get("exclude_kernel_from_recovery_image") == "true":
1502 logger.info("Excluded kernel binary from recovery image.")
1503 else:
1504 kernel = "kernel"
Steve Muckle9793cf62020-04-08 18:27:00 -07001505 else:
1506 kernel = image_name.replace("boot", "kernel")
Kelvin Zhang0876c412020-06-23 15:06:58 -04001507 kernel = kernel.replace(".img", "")
Yifan Hong63c5ca12020-10-08 11:54:02 -07001508 if kernel and not os.access(os.path.join(sourcedir, kernel), os.F_OK):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001509 return None
1510
1511 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001512 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001513
Doug Zongkereef39442009-04-02 12:14:19 -07001514 img = tempfile.NamedTemporaryFile()
1515
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001516 if has_ramdisk:
jiajia tang836f76b2021-04-02 14:48:26 +08001517 ramdisk_format = _GetRamdiskFormat(info_dict)
1518 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file,
1519 ramdisk_format=ramdisk_format)
Doug Zongkereef39442009-04-02 12:14:19 -07001520
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001521 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1522 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1523
Yifan Hong63c5ca12020-10-08 11:54:02 -07001524 cmd = [mkbootimg]
1525 if kernel:
1526 cmd += ["--kernel", os.path.join(sourcedir, kernel)]
Doug Zongker38a649f2009-06-17 09:07:09 -07001527
Benoit Fradina45a8682014-07-14 21:00:43 +02001528 fn = os.path.join(sourcedir, "second")
1529 if os.access(fn, os.F_OK):
1530 cmd.append("--second")
1531 cmd.append(fn)
1532
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001533 fn = os.path.join(sourcedir, "dtb")
1534 if os.access(fn, os.F_OK):
1535 cmd.append("--dtb")
1536 cmd.append(fn)
1537
Doug Zongker171f1cd2009-06-15 22:36:37 -07001538 fn = os.path.join(sourcedir, "cmdline")
1539 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001540 cmd.append("--cmdline")
1541 cmd.append(open(fn).read().rstrip("\n"))
1542
1543 fn = os.path.join(sourcedir, "base")
1544 if os.access(fn, os.F_OK):
1545 cmd.append("--base")
1546 cmd.append(open(fn).read().rstrip("\n"))
1547
Ying Wang4de6b5b2010-08-25 14:29:34 -07001548 fn = os.path.join(sourcedir, "pagesize")
1549 if os.access(fn, os.F_OK):
1550 cmd.append("--pagesize")
1551 cmd.append(open(fn).read().rstrip("\n"))
1552
Steve Mucklef84668e2020-03-16 19:13:46 -07001553 if partition_name == "recovery":
1554 args = info_dict.get("recovery_mkbootimg_args")
P.Adarsh Reddyd8e24ee2020-05-04 19:40:16 +05301555 if not args:
1556 # Fall back to "mkbootimg_args" for recovery image
1557 # in case "recovery_mkbootimg_args" is not set.
1558 args = info_dict.get("mkbootimg_args")
Steve Mucklef84668e2020-03-16 19:13:46 -07001559 else:
1560 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001561 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001562 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001563
Tao Bao76def242017-11-21 09:25:31 -08001564 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001565 if args and args.strip():
1566 cmd.extend(shlex.split(args))
1567
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001568 if has_ramdisk:
1569 cmd.extend(["--ramdisk", ramdisk_img.name])
1570
Bowgo Tsai27c39b02021-03-12 21:40:32 +08001571 AppendGkiSigningArgs(cmd)
1572
Tao Baod95e9fd2015-03-29 23:07:41 -07001573 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001574 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001575 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001576 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001577 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001578 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001579
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001580 if partition_name == "recovery":
1581 if info_dict.get("include_recovery_dtbo") == "true":
1582 fn = os.path.join(sourcedir, "recovery_dtbo")
1583 cmd.extend(["--recovery_dtbo", fn])
1584 if info_dict.get("include_recovery_acpio") == "true":
1585 fn = os.path.join(sourcedir, "recovery_acpio")
1586 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001587
Tao Bao986ee862018-10-04 15:46:16 -07001588 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001589
Tao Bao76def242017-11-21 09:25:31 -08001590 if (info_dict.get("boot_signer") == "true" and
Daniel Norman2d7989a2021-04-05 17:40:47 +00001591 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001592 # Hard-code the path as "/boot" for two-step special recovery image (which
1593 # will be loaded into /boot during the two-step OTA).
1594 if two_step_image:
1595 path = "/boot"
1596 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001597 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001598 cmd = [OPTIONS.boot_signer_path]
1599 cmd.extend(OPTIONS.boot_signer_args)
1600 cmd.extend([path, img.name,
1601 info_dict["verity_key"] + ".pk8",
1602 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001603 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001604
Tao Baod95e9fd2015-03-29 23:07:41 -07001605 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001606 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001607 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001608 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001609 # We have switched from the prebuilt futility binary to using the tool
1610 # (futility-host) built from the source. Override the setting in the old
1611 # TF.zip.
1612 futility = info_dict["futility"]
1613 if futility.startswith("prebuilts/"):
1614 futility = "futility-host"
1615 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001616 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001617 info_dict["vboot_key"] + ".vbprivk",
1618 info_dict["vboot_subkey"] + ".vbprivk",
1619 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001620 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001621 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001622
Tao Baof3282b42015-04-01 11:21:55 -07001623 # Clean up the temp files.
1624 img_unsigned.close()
1625 img_keyblock.close()
1626
David Zeuthen8fecb282017-12-01 16:24:01 -05001627 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001628 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001629 avbtool = info_dict["avb_avbtool"]
Steve Muckle903a1ca2020-05-07 17:32:10 -07001630 if partition_name == "recovery":
1631 part_size = info_dict["recovery_size"]
1632 else:
Kelvin Zhang0876c412020-06-23 15:06:58 -04001633 part_size = info_dict[image_name.replace(".img", "_size")]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001634 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001635 "--partition_size", str(part_size), "--partition_name",
1636 partition_name]
1637 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001638 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001639 if args and args.strip():
1640 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001641 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001642
1643 img.seek(os.SEEK_SET, 0)
1644 data = img.read()
1645
1646 if has_ramdisk:
1647 ramdisk_img.close()
1648 img.close()
1649
1650 return data
1651
1652
Doug Zongkerd5131602012-08-02 14:46:42 -07001653def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001654 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001655 """Return a File object with the desired bootable image.
1656
1657 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1658 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1659 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001660
Doug Zongker55d93282011-01-25 17:03:34 -08001661 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1662 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001663 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001664 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001665
1666 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1667 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001668 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001669 return File.FromLocalFile(name, prebuilt_path)
1670
Tao Bao32fcdab2018-10-12 10:30:39 -07001671 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001672
1673 if info_dict is None:
1674 info_dict = OPTIONS.info_dict
1675
1676 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001677 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1678 # for recovery.
1679 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1680 prebuilt_name != "boot.img" or
1681 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001682
Doug Zongker6f1d0312014-08-22 08:07:12 -07001683 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
Steve Muckle9793cf62020-04-08 18:27:00 -07001684 data = _BuildBootableImage(prebuilt_name, os.path.join(unpack_dir, tree_subdir),
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001685 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001686 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001687 if data:
1688 return File(name, data)
1689 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001690
Doug Zongkereef39442009-04-02 12:14:19 -07001691
Steve Mucklee1b10862019-07-10 10:49:37 -07001692def _BuildVendorBootImage(sourcedir, info_dict=None):
1693 """Build a vendor boot image from the specified sourcedir.
1694
1695 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1696 turn them into a vendor boot image.
1697
1698 Return the image data, or None if sourcedir does not appear to contains files
1699 for building the requested image.
1700 """
1701
1702 if info_dict is None:
1703 info_dict = OPTIONS.info_dict
1704
1705 img = tempfile.NamedTemporaryFile()
1706
jiajia tang836f76b2021-04-02 14:48:26 +08001707 ramdisk_format = _GetRamdiskFormat(info_dict)
1708 ramdisk_img = _MakeRamdisk(sourcedir, ramdisk_format=ramdisk_format)
Steve Mucklee1b10862019-07-10 10:49:37 -07001709
1710 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1711 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1712
1713 cmd = [mkbootimg]
1714
1715 fn = os.path.join(sourcedir, "dtb")
1716 if os.access(fn, os.F_OK):
1717 cmd.append("--dtb")
1718 cmd.append(fn)
1719
1720 fn = os.path.join(sourcedir, "vendor_cmdline")
1721 if os.access(fn, os.F_OK):
1722 cmd.append("--vendor_cmdline")
1723 cmd.append(open(fn).read().rstrip("\n"))
1724
1725 fn = os.path.join(sourcedir, "base")
1726 if os.access(fn, os.F_OK):
1727 cmd.append("--base")
1728 cmd.append(open(fn).read().rstrip("\n"))
1729
1730 fn = os.path.join(sourcedir, "pagesize")
1731 if os.access(fn, os.F_OK):
1732 cmd.append("--pagesize")
1733 cmd.append(open(fn).read().rstrip("\n"))
1734
1735 args = info_dict.get("mkbootimg_args")
1736 if args and args.strip():
1737 cmd.extend(shlex.split(args))
1738
1739 args = info_dict.get("mkbootimg_version_args")
1740 if args and args.strip():
1741 cmd.extend(shlex.split(args))
1742
1743 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1744 cmd.extend(["--vendor_boot", img.name])
1745
Devin Moore50509012021-01-13 10:45:04 -08001746 fn = os.path.join(sourcedir, "vendor_bootconfig")
1747 if os.access(fn, os.F_OK):
1748 cmd.append("--vendor_bootconfig")
1749 cmd.append(fn)
1750
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001751 ramdisk_fragment_imgs = []
1752 fn = os.path.join(sourcedir, "vendor_ramdisk_fragments")
1753 if os.access(fn, os.F_OK):
1754 ramdisk_fragments = shlex.split(open(fn).read().rstrip("\n"))
1755 for ramdisk_fragment in ramdisk_fragments:
Daniel Norman2d7989a2021-04-05 17:40:47 +00001756 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "mkbootimg_args")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001757 cmd.extend(shlex.split(open(fn).read().rstrip("\n")))
Daniel Norman2d7989a2021-04-05 17:40:47 +00001758 fn = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment, "prebuilt_ramdisk")
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001759 # Use prebuilt image if found, else create ramdisk from supplied files.
1760 if os.access(fn, os.F_OK):
1761 ramdisk_fragment_pathname = fn
1762 else:
Daniel Norman2d7989a2021-04-05 17:40:47 +00001763 ramdisk_fragment_root = os.path.join(sourcedir, "RAMDISK_FRAGMENTS", ramdisk_fragment)
jiajia tang836f76b2021-04-02 14:48:26 +08001764 ramdisk_fragment_img = _MakeRamdisk(ramdisk_fragment_root,
1765 ramdisk_format=ramdisk_format)
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001766 ramdisk_fragment_imgs.append(ramdisk_fragment_img)
1767 ramdisk_fragment_pathname = ramdisk_fragment_img.name
1768 cmd.extend(["--vendor_ramdisk_fragment", ramdisk_fragment_pathname])
1769
Steve Mucklee1b10862019-07-10 10:49:37 -07001770 RunAndCheckOutput(cmd)
1771
1772 # AVB: if enabled, calculate and add hash.
1773 if info_dict.get("avb_enable") == "true":
1774 avbtool = info_dict["avb_avbtool"]
1775 part_size = info_dict["vendor_boot_size"]
1776 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001777 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001778 AppendAVBSigningArgs(cmd, "vendor_boot")
1779 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1780 if args and args.strip():
1781 cmd.extend(shlex.split(args))
1782 RunAndCheckOutput(cmd)
1783
1784 img.seek(os.SEEK_SET, 0)
1785 data = img.read()
1786
Yo Chiangd21e7dc2020-12-10 18:42:47 +08001787 for f in ramdisk_fragment_imgs:
1788 f.close()
Steve Mucklee1b10862019-07-10 10:49:37 -07001789 ramdisk_img.close()
1790 img.close()
1791
1792 return data
1793
1794
1795def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1796 info_dict=None):
1797 """Return a File object with the desired vendor boot image.
1798
1799 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1800 the source files in 'unpack_dir'/'tree_subdir'."""
1801
1802 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1803 if os.path.exists(prebuilt_path):
1804 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1805 return File.FromLocalFile(name, prebuilt_path)
1806
1807 logger.info("building image from target_files %s...", tree_subdir)
1808
1809 if info_dict is None:
1810 info_dict = OPTIONS.info_dict
1811
Kelvin Zhang0876c412020-06-23 15:06:58 -04001812 data = _BuildVendorBootImage(
1813 os.path.join(unpack_dir, tree_subdir), info_dict)
Steve Mucklee1b10862019-07-10 10:49:37 -07001814 if data:
1815 return File(name, data)
1816 return None
1817
1818
Narayan Kamatha07bf042017-08-14 14:49:21 +01001819def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001820 """Gunzips the given gzip compressed file to a given output file."""
1821 with gzip.open(in_filename, "rb") as in_file, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001822 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001823 shutil.copyfileobj(in_file, out_file)
1824
1825
Tao Bao0ff15de2019-03-20 11:26:06 -07001826def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001827 """Unzips the archive to the given directory.
1828
1829 Args:
1830 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001831 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001832 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1833 archvie. Non-matching patterns will be filtered out. If there's no match
1834 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001835 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001836 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001837 if patterns is not None:
1838 # Filter out non-matching patterns. unzip will complain otherwise.
Kelvin Zhang928c2342020-09-22 16:15:57 -04001839 with zipfile.ZipFile(filename, allowZip64=True) as input_zip:
Tao Bao0ff15de2019-03-20 11:26:06 -07001840 names = input_zip.namelist()
1841 filtered = [
1842 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1843
1844 # There isn't any matching files. Don't unzip anything.
1845 if not filtered:
1846 return
1847 cmd.extend(filtered)
1848
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001849 RunAndCheckOutput(cmd)
1850
1851
Doug Zongker75f17362009-12-08 13:46:44 -08001852def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001853 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001854
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001855 Args:
1856 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1857 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1858
1859 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1860 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001861
Tao Bao1c830bf2017-12-25 10:43:47 -08001862 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001863 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001864 """
Doug Zongkereef39442009-04-02 12:14:19 -07001865
Tao Bao1c830bf2017-12-25 10:43:47 -08001866 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001867 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1868 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001869 UnzipToDir(m.group(1), tmp, pattern)
1870 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001871 filename = m.group(1)
1872 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001873 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001874
Tao Baodba59ee2018-01-09 13:21:02 -08001875 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001876
1877
Yifan Hong8a66a712019-04-04 15:37:57 -07001878def GetUserImage(which, tmpdir, input_zip,
1879 info_dict=None,
1880 allow_shared_blocks=None,
1881 hashtree_info_generator=None,
1882 reset_file_map=False):
1883 """Returns an Image object suitable for passing to BlockImageDiff.
1884
1885 This function loads the specified image from the given path. If the specified
1886 image is sparse, it also performs additional processing for OTA purpose. For
1887 example, it always adds block 0 to clobbered blocks list. It also detects
1888 files that cannot be reconstructed from the block list, for whom we should
1889 avoid applying imgdiff.
1890
1891 Args:
1892 which: The partition name.
1893 tmpdir: The directory that contains the prebuilt image and block map file.
1894 input_zip: The target-files ZIP archive.
1895 info_dict: The dict to be looked up for relevant info.
1896 allow_shared_blocks: If image is sparse, whether having shared blocks is
1897 allowed. If none, it is looked up from info_dict.
1898 hashtree_info_generator: If present and image is sparse, generates the
1899 hashtree_info for this sparse image.
1900 reset_file_map: If true and image is sparse, reset file map before returning
1901 the image.
1902 Returns:
1903 A Image object. If it is a sparse image and reset_file_map is False, the
1904 image will have file_map info loaded.
1905 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001906 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001907 info_dict = LoadInfoDict(input_zip)
1908
1909 is_sparse = info_dict.get("extfs_sparse_flag")
1910
1911 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1912 # shared blocks (i.e. some blocks will show up in multiple files' block
1913 # list). We can only allocate such shared blocks to the first "owner", and
1914 # disable imgdiff for all later occurrences.
1915 if allow_shared_blocks is None:
1916 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1917
1918 if is_sparse:
1919 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1920 hashtree_info_generator)
1921 if reset_file_map:
1922 img.ResetFileMap()
1923 return img
Kelvin Zhang0876c412020-06-23 15:06:58 -04001924 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
Yifan Hong8a66a712019-04-04 15:37:57 -07001925
1926
1927def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1928 """Returns a Image object suitable for passing to BlockImageDiff.
1929
1930 This function loads the specified non-sparse image from the given path.
1931
1932 Args:
1933 which: The partition name.
1934 tmpdir: The directory that contains the prebuilt image and block map file.
1935 Returns:
1936 A Image object.
1937 """
1938 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1939 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1940
1941 # The image and map files must have been created prior to calling
1942 # ota_from_target_files.py (since LMP).
1943 assert os.path.exists(path) and os.path.exists(mappath)
1944
Tianjie Xu41976c72019-07-03 13:57:01 -07001945 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1946
Yifan Hong8a66a712019-04-04 15:37:57 -07001947
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001948def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1949 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001950 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1951
1952 This function loads the specified sparse image from the given path, and
1953 performs additional processing for OTA purpose. For example, it always adds
1954 block 0 to clobbered blocks list. It also detects files that cannot be
1955 reconstructed from the block list, for whom we should avoid applying imgdiff.
1956
1957 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001958 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001959 tmpdir: The directory that contains the prebuilt image and block map file.
1960 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001961 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001962 hashtree_info_generator: If present, generates the hashtree_info for this
1963 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001964 Returns:
1965 A SparseImage object, with file_map info loaded.
1966 """
Tao Baoc765cca2018-01-31 17:32:40 -08001967 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1968 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1969
1970 # The image and map files must have been created prior to calling
1971 # ota_from_target_files.py (since LMP).
1972 assert os.path.exists(path) and os.path.exists(mappath)
1973
1974 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1975 # it to clobbered_blocks so that it will be written to the target
1976 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1977 clobbered_blocks = "0"
1978
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001979 image = sparse_img.SparseImage(
1980 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1981 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001982
1983 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1984 # if they contain all zeros. We can't reconstruct such a file from its block
1985 # list. Tag such entries accordingly. (Bug: 65213616)
1986 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001987 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001988 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001989 continue
1990
Tom Cherryd14b8952018-08-09 14:26:00 -07001991 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1992 # filename listed in system.map may contain an additional leading slash
1993 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1994 # results.
wangshumin71af07a2021-02-24 11:08:47 +08001995 # And handle another special case, where files not under /system
Tom Cherryd14b8952018-08-09 14:26:00 -07001996 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
wangshumin71af07a2021-02-24 11:08:47 +08001997 arcname = entry.lstrip('/')
1998 if which == 'system' and not arcname.startswith('system'):
Tao Baod3554e62018-07-10 15:31:22 -07001999 arcname = 'ROOT/' + arcname
wangshumin71af07a2021-02-24 11:08:47 +08002000 else:
2001 arcname = arcname.replace(which, which.upper(), 1)
Tao Baod3554e62018-07-10 15:31:22 -07002002
2003 assert arcname in input_zip.namelist(), \
2004 "Failed to find the ZIP entry for {}".format(entry)
2005
Tao Baoc765cca2018-01-31 17:32:40 -08002006 info = input_zip.getinfo(arcname)
2007 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08002008
2009 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08002010 # image, check the original block list to determine its completeness. Note
2011 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08002012 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08002013 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08002014
Tao Baoc765cca2018-01-31 17:32:40 -08002015 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
2016 ranges.extra['incomplete'] = True
2017
2018 return image
2019
2020
Doug Zongkereef39442009-04-02 12:14:19 -07002021def GetKeyPasswords(keylist):
2022 """Given a list of keys, prompt the user to enter passwords for
2023 those which require them. Return a {key: password} dict. password
2024 will be None if the key has no password."""
2025
Doug Zongker8ce7c252009-05-22 13:34:54 -07002026 no_passwords = []
2027 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07002028 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07002029 devnull = open("/dev/null", "w+b")
2030 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002031 # We don't need a password for things that aren't really keys.
2032 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002033 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07002034 continue
2035
T.R. Fullhart37e10522013-03-18 10:31:26 -07002036 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07002037 "-inform", "DER", "-nocrypt"],
2038 stdin=devnull.fileno(),
2039 stdout=devnull.fileno(),
2040 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07002041 p.communicate()
2042 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002043 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07002044 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002045 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07002046 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
2047 "-inform", "DER", "-passin", "pass:"],
2048 stdin=devnull.fileno(),
2049 stdout=devnull.fileno(),
2050 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07002051 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07002052 if p.returncode == 0:
2053 # Encrypted key with empty string as password.
2054 key_passwords[k] = ''
2055 elif stderr.startswith('Error decrypting key'):
2056 # Definitely encrypted key.
2057 # It would have said "Error reading key" if it didn't parse correctly.
2058 need_passwords.append(k)
2059 else:
2060 # Potentially, a type of key that openssl doesn't understand.
2061 # We'll let the routines in signapk.jar handle it.
2062 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07002063 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07002064
T.R. Fullhart37e10522013-03-18 10:31:26 -07002065 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08002066 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07002067 return key_passwords
2068
2069
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002070def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07002071 """Gets the minSdkVersion declared in the APK.
2072
changho.shin0f125362019-07-08 10:59:00 +09002073 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07002074 This can be both a decimal number (API Level) or a codename.
2075
2076 Args:
2077 apk_name: The APK filename.
2078
2079 Returns:
2080 The parsed SDK version string.
2081
2082 Raises:
2083 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002084 """
Tao Baof47bf0f2018-03-21 23:28:51 -07002085 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09002086 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07002087 stderr=subprocess.PIPE)
2088 stdoutdata, stderrdata = proc.communicate()
2089 if proc.returncode != 0:
2090 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09002091 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07002092 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002093
Tao Baof47bf0f2018-03-21 23:28:51 -07002094 for line in stdoutdata.split("\n"):
2095 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002096 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
2097 if m:
2098 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09002099 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002100
2101
2102def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07002103 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002104
Tao Baof47bf0f2018-03-21 23:28:51 -07002105 If minSdkVersion is set to a codename, it is translated to a number using the
2106 provided map.
2107
2108 Args:
2109 apk_name: The APK filename.
2110
2111 Returns:
2112 The parsed SDK version number.
2113
2114 Raises:
2115 ExternalError: On failing to get the min SDK version number.
2116 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002117 version = GetMinSdkVersion(apk_name)
2118 try:
2119 return int(version)
2120 except ValueError:
2121 # Not a decimal number. Codename?
2122 if version in codename_to_api_level_map:
2123 return codename_to_api_level_map[version]
Kelvin Zhang0876c412020-06-23 15:06:58 -04002124 raise ExternalError(
2125 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
2126 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002127
2128
2129def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07002130 codename_to_api_level_map=None, whole_file=False,
2131 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07002132 """Sign the input_name zip/jar/apk, producing output_name. Use the
2133 given key and password (the latter may be None if the key does not
2134 have a password.
2135
Doug Zongker951495f2009-08-14 12:44:19 -07002136 If whole_file is true, use the "-w" option to SignApk to embed a
2137 signature that covers the whole file in the archive comment of the
2138 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002139
2140 min_api_level is the API Level (int) of the oldest platform this file may end
2141 up on. If not specified for an APK, the API Level is obtained by interpreting
2142 the minSdkVersion attribute of the APK's AndroidManifest.xml.
2143
2144 codename_to_api_level_map is needed to translate the codename which may be
2145 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07002146
2147 Caller may optionally specify extra args to be passed to SignApk, which
2148 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07002149 """
Tao Bao76def242017-11-21 09:25:31 -08002150 if codename_to_api_level_map is None:
2151 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07002152 if extra_signapk_args is None:
2153 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07002154
Alex Klyubin9667b182015-12-10 13:38:50 -08002155 java_library_path = os.path.join(
2156 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
2157
Tao Baoe95540e2016-11-08 12:08:53 -08002158 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
2159 ["-Djava.library.path=" + java_library_path,
2160 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07002161 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07002162 if whole_file:
2163 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08002164
2165 min_sdk_version = min_api_level
2166 if min_sdk_version is None:
2167 if not whole_file:
2168 min_sdk_version = GetMinSdkVersionInt(
2169 input_name, codename_to_api_level_map)
2170 if min_sdk_version is not None:
2171 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
2172
T.R. Fullhart37e10522013-03-18 10:31:26 -07002173 cmd.extend([key + OPTIONS.public_key_suffix,
2174 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08002175 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07002176
Tao Bao73dd4f42018-10-04 16:25:33 -07002177 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07002178 if password is not None:
2179 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07002180 stdoutdata, _ = proc.communicate(password)
2181 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07002182 raise ExternalError(
2183 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07002184 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07002185
Doug Zongkereef39442009-04-02 12:14:19 -07002186
Doug Zongker37974732010-09-16 17:44:38 -07002187def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08002188 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07002189
Tao Bao9dd909e2017-11-14 11:27:32 -08002190 For non-AVB images, raise exception if the data is too big. Print a warning
2191 if the data is nearing the maximum size.
2192
2193 For AVB images, the actual image size should be identical to the limit.
2194
2195 Args:
2196 data: A string that contains all the data for the partition.
2197 target: The partition name. The ".img" suffix is optional.
2198 info_dict: The dict to be looked up for relevant info.
2199 """
Dan Albert8b72aef2015-03-23 19:13:21 -07002200 if target.endswith(".img"):
2201 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002202 mount_point = "/" + target
2203
Ying Wangf8824af2014-06-03 14:07:27 -07002204 fs_type = None
2205 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002206 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07002207 if mount_point == "/userdata":
2208 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07002209 p = info_dict["fstab"][mount_point]
2210 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08002211 device = p.device
2212 if "/" in device:
2213 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08002214 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07002215 if not fs_type or not limit:
2216 return
Doug Zongkereef39442009-04-02 12:14:19 -07002217
Andrew Boie0f9aec82012-02-14 09:32:52 -08002218 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08002219 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
2220 # path.
2221 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
2222 if size != limit:
2223 raise ExternalError(
2224 "Mismatching image size for %s: expected %d actual %d" % (
2225 target, limit, size))
2226 else:
2227 pct = float(size) * 100.0 / limit
2228 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
2229 if pct >= 99.0:
2230 raise ExternalError(msg)
Kelvin Zhang0876c412020-06-23 15:06:58 -04002231
2232 if pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002233 logger.warning("\n WARNING: %s\n", msg)
2234 else:
2235 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07002236
2237
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002238def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08002239 """Parses the APK certs info from a given target-files zip.
2240
2241 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
2242 tuple with the following elements: (1) a dictionary that maps packages to
2243 certs (based on the "certificate" and "private_key" attributes in the file;
2244 (2) a string representing the extension of compressed APKs in the target files
2245 (e.g ".gz", ".bro").
2246
2247 Args:
2248 tf_zip: The input target_files ZipFile (already open).
2249
2250 Returns:
2251 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
2252 the extension string of compressed APKs (e.g. ".gz"), or None if there's
2253 no compressed APKs.
2254 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002255 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01002256 compressed_extension = None
2257
Tao Bao0f990332017-09-08 19:02:54 -07002258 # META/apkcerts.txt contains the info for _all_ the packages known at build
2259 # time. Filter out the ones that are not installed.
2260 installed_files = set()
2261 for name in tf_zip.namelist():
2262 basename = os.path.basename(name)
2263 if basename:
2264 installed_files.add(basename)
2265
Tao Baoda30cfa2017-12-01 16:19:46 -08002266 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002267 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002268 if not line:
2269 continue
Tao Bao818ddf52018-01-05 11:17:34 -08002270 m = re.match(
2271 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
Bill Peckham5c7b0342020-04-03 15:36:23 -07002272 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*?)")?'
2273 r'(\s+partition="(?P<PARTITION>.*?)")?$',
Tao Bao818ddf52018-01-05 11:17:34 -08002274 line)
2275 if not m:
2276 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01002277
Tao Bao818ddf52018-01-05 11:17:34 -08002278 matches = m.groupdict()
2279 cert = matches["CERT"]
2280 privkey = matches["PRIVKEY"]
2281 name = matches["NAME"]
2282 this_compressed_extension = matches["COMPRESSED"]
2283
2284 public_key_suffix_len = len(OPTIONS.public_key_suffix)
2285 private_key_suffix_len = len(OPTIONS.private_key_suffix)
2286 if cert in SPECIAL_CERT_STRINGS and not privkey:
2287 certmap[name] = cert
2288 elif (cert.endswith(OPTIONS.public_key_suffix) and
2289 privkey.endswith(OPTIONS.private_key_suffix) and
2290 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
2291 certmap[name] = cert[:-public_key_suffix_len]
2292 else:
2293 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
2294
2295 if not this_compressed_extension:
2296 continue
2297
2298 # Only count the installed files.
2299 filename = name + '.' + this_compressed_extension
2300 if filename not in installed_files:
2301 continue
2302
2303 # Make sure that all the values in the compression map have the same
2304 # extension. We don't support multiple compression methods in the same
2305 # system image.
2306 if compressed_extension:
2307 if this_compressed_extension != compressed_extension:
2308 raise ValueError(
2309 "Multiple compressed extensions: {} vs {}".format(
2310 compressed_extension, this_compressed_extension))
2311 else:
2312 compressed_extension = this_compressed_extension
2313
2314 return (certmap,
2315 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08002316
2317
Doug Zongkereef39442009-04-02 12:14:19 -07002318COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07002319Global options
2320
2321 -p (--path) <dir>
2322 Prepend <dir>/bin to the list of places to search for binaries run by this
2323 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07002324
Doug Zongker05d3dea2009-06-22 11:32:31 -07002325 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07002326 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07002327
Tao Bao30df8b42018-04-23 15:32:53 -07002328 -x (--extra) <key=value>
2329 Add a key/value pair to the 'extras' dict, which device-specific extension
2330 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08002331
Doug Zongkereef39442009-04-02 12:14:19 -07002332 -v (--verbose)
2333 Show command lines being executed.
2334
2335 -h (--help)
2336 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07002337
2338 --logfile <file>
2339 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07002340"""
2341
Kelvin Zhang0876c412020-06-23 15:06:58 -04002342
Doug Zongkereef39442009-04-02 12:14:19 -07002343def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08002344 print(docstring.rstrip("\n"))
2345 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07002346
2347
2348def ParseOptions(argv,
2349 docstring,
2350 extra_opts="", extra_long_opts=(),
2351 extra_option_handler=None):
2352 """Parse the options in argv and return any arguments that aren't
2353 flags. docstring is the calling module's docstring, to be displayed
2354 for errors and -h. extra_opts and extra_long_opts are for flags
2355 defined by the caller, which are processed by passing them to
2356 extra_option_handler."""
2357
2358 try:
2359 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08002360 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08002361 ["help", "verbose", "path=", "signapk_path=",
2362 "signapk_shared_library_path=", "extra_signapk_args=",
Tianjie Xu88a759d2020-01-23 10:47:54 -08002363 "java_path=", "java_args=", "android_jar_path=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07002364 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
2365 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Tianjie0f307452020-04-01 12:20:21 -07002366 "extra=", "logfile=", "aftl_tool_path=", "aftl_server=",
2367 "aftl_key_path=", "aftl_manufacturer_key_path=",
2368 "aftl_signer_helper="] + list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07002369 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07002370 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08002371 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07002372 sys.exit(2)
2373
Doug Zongkereef39442009-04-02 12:14:19 -07002374 for o, a in opts:
2375 if o in ("-h", "--help"):
2376 Usage(docstring)
2377 sys.exit()
2378 elif o in ("-v", "--verbose"):
2379 OPTIONS.verbose = True
2380 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07002381 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002382 elif o in ("--signapk_path",):
2383 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08002384 elif o in ("--signapk_shared_library_path",):
2385 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002386 elif o in ("--extra_signapk_args",):
2387 OPTIONS.extra_signapk_args = shlex.split(a)
2388 elif o in ("--java_path",):
2389 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07002390 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08002391 OPTIONS.java_args = shlex.split(a)
Tianjie Xu88a759d2020-01-23 10:47:54 -08002392 elif o in ("--android_jar_path",):
2393 OPTIONS.android_jar_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07002394 elif o in ("--public_key_suffix",):
2395 OPTIONS.public_key_suffix = a
2396 elif o in ("--private_key_suffix",):
2397 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08002398 elif o in ("--boot_signer_path",):
2399 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07002400 elif o in ("--boot_signer_args",):
2401 OPTIONS.boot_signer_args = shlex.split(a)
2402 elif o in ("--verity_signer_path",):
2403 OPTIONS.verity_signer_path = a
2404 elif o in ("--verity_signer_args",):
2405 OPTIONS.verity_signer_args = shlex.split(a)
Tianjie0f307452020-04-01 12:20:21 -07002406 elif o in ("--aftl_tool_path",):
2407 OPTIONS.aftl_tool_path = a
Dan Austin52903642019-12-12 15:44:00 -08002408 elif o in ("--aftl_server",):
2409 OPTIONS.aftl_server = a
2410 elif o in ("--aftl_key_path",):
2411 OPTIONS.aftl_key_path = a
2412 elif o in ("--aftl_manufacturer_key_path",):
2413 OPTIONS.aftl_manufacturer_key_path = a
2414 elif o in ("--aftl_signer_helper",):
2415 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07002416 elif o in ("-s", "--device_specific"):
2417 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08002418 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08002419 key, value = a.split("=", 1)
2420 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07002421 elif o in ("--logfile",):
2422 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07002423 else:
2424 if extra_option_handler is None or not extra_option_handler(o, a):
2425 assert False, "unknown option \"%s\"" % (o,)
2426
Doug Zongker85448772014-09-09 14:59:20 -07002427 if OPTIONS.search_path:
2428 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
2429 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07002430
2431 return args
2432
2433
Tao Bao4c851b12016-09-19 13:54:38 -07002434def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07002435 """Make a temp file and add it to the list of things to be deleted
2436 when Cleanup() is called. Return the filename."""
2437 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
2438 os.close(fd)
2439 OPTIONS.tempfiles.append(fn)
2440 return fn
2441
2442
Tao Bao1c830bf2017-12-25 10:43:47 -08002443def MakeTempDir(prefix='tmp', suffix=''):
2444 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
2445
2446 Returns:
2447 The absolute pathname of the new directory.
2448 """
2449 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
2450 OPTIONS.tempfiles.append(dir_name)
2451 return dir_name
2452
2453
Doug Zongkereef39442009-04-02 12:14:19 -07002454def Cleanup():
2455 for i in OPTIONS.tempfiles:
2456 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08002457 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07002458 else:
2459 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08002460 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07002461
2462
2463class PasswordManager(object):
2464 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08002465 self.editor = os.getenv("EDITOR")
2466 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002467
2468 def GetPasswords(self, items):
2469 """Get passwords corresponding to each string in 'items',
2470 returning a dict. (The dict may have keys in addition to the
2471 values in 'items'.)
2472
2473 Uses the passwords in $ANDROID_PW_FILE if available, letting the
2474 user edit that file to add more needed passwords. If no editor is
2475 available, or $ANDROID_PW_FILE isn't define, prompts the user
2476 interactively in the ordinary way.
2477 """
2478
2479 current = self.ReadFile()
2480
2481 first = True
2482 while True:
2483 missing = []
2484 for i in items:
2485 if i not in current or not current[i]:
2486 missing.append(i)
2487 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07002488 if not missing:
2489 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07002490
2491 for i in missing:
2492 current[i] = ""
2493
2494 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08002495 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08002496 if sys.version_info[0] >= 3:
2497 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07002498 answer = raw_input("try to edit again? [y]> ").strip()
2499 if answer and answer[0] not in 'yY':
2500 raise RuntimeError("key passwords unavailable")
2501 first = False
2502
2503 current = self.UpdateAndReadFile(current)
2504
Kelvin Zhang0876c412020-06-23 15:06:58 -04002505 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07002506 """Prompt the user to enter a value (password) for each key in
2507 'current' whose value is fales. Returns a new dict with all the
2508 values.
2509 """
2510 result = {}
Tao Bao38884282019-07-10 22:20:56 -07002511 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002512 if v:
2513 result[k] = v
2514 else:
2515 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07002516 result[k] = getpass.getpass(
2517 "Enter password for %s key> " % k).strip()
2518 if result[k]:
2519 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07002520 return result
2521
2522 def UpdateAndReadFile(self, current):
2523 if not self.editor or not self.pwfile:
2524 return self.PromptResult(current)
2525
2526 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07002527 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002528 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
2529 f.write("# (Additional spaces are harmless.)\n\n")
2530
2531 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07002532 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07002533 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07002534 f.write("[[[ %s ]]] %s\n" % (v, k))
2535 if not v and first_line is None:
2536 # position cursor on first line with no password.
2537 first_line = i + 4
2538 f.close()
2539
Tao Bao986ee862018-10-04 15:46:16 -07002540 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002541
2542 return self.ReadFile()
2543
2544 def ReadFile(self):
2545 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002546 if self.pwfile is None:
2547 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002548 try:
2549 f = open(self.pwfile, "r")
2550 for line in f:
2551 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002552 if not line or line[0] == '#':
2553 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002554 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2555 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002556 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002557 else:
2558 result[m.group(2)] = m.group(1)
2559 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002560 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002561 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002562 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002563 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002564
2565
Dan Albert8e0178d2015-01-27 15:53:15 -08002566def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2567 compress_type=None):
Dan Albert8e0178d2015-01-27 15:53:15 -08002568
2569 # http://b/18015246
2570 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2571 # for files larger than 2GiB. We can work around this by adjusting their
2572 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2573 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2574 # it isn't clear to me exactly what circumstances cause this).
2575 # `zipfile.write()` must be used directly to work around this.
2576 #
2577 # This mess can be avoided if we port to python3.
2578 saved_zip64_limit = zipfile.ZIP64_LIMIT
2579 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2580
2581 if compress_type is None:
2582 compress_type = zip_file.compression
2583 if arcname is None:
2584 arcname = filename
2585
2586 saved_stat = os.stat(filename)
2587
2588 try:
2589 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2590 # file to be zipped and reset it when we're done.
2591 os.chmod(filename, perms)
2592
2593 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002594 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2595 # intentional. zip stores datetimes in local time without a time zone
2596 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2597 # in the zip archive.
2598 local_epoch = datetime.datetime.fromtimestamp(0)
2599 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002600 os.utime(filename, (timestamp, timestamp))
2601
2602 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2603 finally:
2604 os.chmod(filename, saved_stat.st_mode)
2605 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2606 zipfile.ZIP64_LIMIT = saved_zip64_limit
2607
2608
Tao Bao58c1b962015-05-20 09:32:18 -07002609def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002610 compress_type=None):
2611 """Wrap zipfile.writestr() function to work around the zip64 limit.
2612
2613 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2614 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2615 when calling crc32(bytes).
2616
2617 But it still works fine to write a shorter string into a large zip file.
2618 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2619 when we know the string won't be too long.
2620 """
2621
2622 saved_zip64_limit = zipfile.ZIP64_LIMIT
2623 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2624
2625 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2626 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002627 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002628 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002629 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002630 else:
Tao Baof3282b42015-04-01 11:21:55 -07002631 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002632 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2633 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2634 # such a case (since
2635 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2636 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2637 # permission bits. We follow the logic in Python 3 to get consistent
2638 # behavior between using the two versions.
2639 if not zinfo.external_attr:
2640 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002641
2642 # If compress_type is given, it overrides the value in zinfo.
2643 if compress_type is not None:
2644 zinfo.compress_type = compress_type
2645
Tao Bao58c1b962015-05-20 09:32:18 -07002646 # If perms is given, it has a priority.
2647 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002648 # If perms doesn't set the file type, mark it as a regular file.
2649 if perms & 0o770000 == 0:
2650 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002651 zinfo.external_attr = perms << 16
2652
Tao Baof3282b42015-04-01 11:21:55 -07002653 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002654 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2655
Dan Albert8b72aef2015-03-23 19:13:21 -07002656 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002657 zipfile.ZIP64_LIMIT = saved_zip64_limit
2658
2659
Tao Bao89d7ab22017-12-14 17:05:33 -08002660def ZipDelete(zip_filename, entries):
2661 """Deletes entries from a ZIP file.
2662
2663 Since deleting entries from a ZIP file is not supported, it shells out to
2664 'zip -d'.
2665
2666 Args:
2667 zip_filename: The name of the ZIP file.
2668 entries: The name of the entry, or the list of names to be deleted.
2669
2670 Raises:
2671 AssertionError: In case of non-zero return from 'zip'.
2672 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002673 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002674 entries = [entries]
2675 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002676 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002677
2678
Tao Baof3282b42015-04-01 11:21:55 -07002679def ZipClose(zip_file):
2680 # http://b/18015246
2681 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2682 # central directory.
2683 saved_zip64_limit = zipfile.ZIP64_LIMIT
2684 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2685
2686 zip_file.close()
2687
2688 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002689
2690
2691class DeviceSpecificParams(object):
2692 module = None
Kelvin Zhang0876c412020-06-23 15:06:58 -04002693
Doug Zongker05d3dea2009-06-22 11:32:31 -07002694 def __init__(self, **kwargs):
2695 """Keyword arguments to the constructor become attributes of this
2696 object, which is passed to all functions in the device-specific
2697 module."""
Tao Bao38884282019-07-10 22:20:56 -07002698 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002699 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002700 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002701
2702 if self.module is None:
2703 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002704 if not path:
2705 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002706 try:
2707 if os.path.isdir(path):
2708 info = imp.find_module("releasetools", [path])
2709 else:
2710 d, f = os.path.split(path)
2711 b, x = os.path.splitext(f)
2712 if x == ".py":
2713 f = b
2714 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002715 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002716 self.module = imp.load_module("device_specific", *info)
2717 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002718 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002719
2720 def _DoCall(self, function_name, *args, **kwargs):
2721 """Call the named function in the device-specific module, passing
2722 the given args and kwargs. The first argument to the call will be
2723 the DeviceSpecific object itself. If there is no module, or the
2724 module does not define the function, return the value of the
2725 'default' kwarg (which itself defaults to None)."""
2726 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002727 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002728 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2729
2730 def FullOTA_Assertions(self):
2731 """Called after emitting the block of assertions at the top of a
2732 full OTA package. Implementations can add whatever additional
2733 assertions they like."""
2734 return self._DoCall("FullOTA_Assertions")
2735
Doug Zongkere5ff5902012-01-17 10:55:37 -08002736 def FullOTA_InstallBegin(self):
2737 """Called at the start of full OTA installation."""
2738 return self._DoCall("FullOTA_InstallBegin")
2739
Yifan Hong10c530d2018-12-27 17:34:18 -08002740 def FullOTA_GetBlockDifferences(self):
2741 """Called during full OTA installation and verification.
2742 Implementation should return a list of BlockDifference objects describing
2743 the update on each additional partitions.
2744 """
2745 return self._DoCall("FullOTA_GetBlockDifferences")
2746
Doug Zongker05d3dea2009-06-22 11:32:31 -07002747 def FullOTA_InstallEnd(self):
2748 """Called at the end of full OTA installation; typically this is
2749 used to install the image for the device's baseband processor."""
2750 return self._DoCall("FullOTA_InstallEnd")
2751
2752 def IncrementalOTA_Assertions(self):
2753 """Called after emitting the block of assertions at the top of an
2754 incremental OTA package. Implementations can add whatever
2755 additional assertions they like."""
2756 return self._DoCall("IncrementalOTA_Assertions")
2757
Doug Zongkere5ff5902012-01-17 10:55:37 -08002758 def IncrementalOTA_VerifyBegin(self):
2759 """Called at the start of the verification phase of incremental
2760 OTA installation; additional checks can be placed here to abort
2761 the script before any changes are made."""
2762 return self._DoCall("IncrementalOTA_VerifyBegin")
2763
Doug Zongker05d3dea2009-06-22 11:32:31 -07002764 def IncrementalOTA_VerifyEnd(self):
2765 """Called at the end of the verification phase of incremental OTA
2766 installation; additional checks can be placed here to abort the
2767 script before any changes are made."""
2768 return self._DoCall("IncrementalOTA_VerifyEnd")
2769
Doug Zongkere5ff5902012-01-17 10:55:37 -08002770 def IncrementalOTA_InstallBegin(self):
2771 """Called at the start of incremental OTA installation (after
2772 verification is complete)."""
2773 return self._DoCall("IncrementalOTA_InstallBegin")
2774
Yifan Hong10c530d2018-12-27 17:34:18 -08002775 def IncrementalOTA_GetBlockDifferences(self):
2776 """Called during incremental OTA installation and verification.
2777 Implementation should return a list of BlockDifference objects describing
2778 the update on each additional partitions.
2779 """
2780 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2781
Doug Zongker05d3dea2009-06-22 11:32:31 -07002782 def IncrementalOTA_InstallEnd(self):
2783 """Called at the end of incremental OTA installation; typically
2784 this is used to install the image for the device's baseband
2785 processor."""
2786 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002787
Tao Bao9bc6bb22015-11-09 16:58:28 -08002788 def VerifyOTA_Assertions(self):
2789 return self._DoCall("VerifyOTA_Assertions")
2790
Tao Bao76def242017-11-21 09:25:31 -08002791
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002792class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002793 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002794 self.name = name
2795 self.data = data
2796 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002797 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002798 self.sha1 = sha1(data).hexdigest()
2799
2800 @classmethod
2801 def FromLocalFile(cls, name, diskname):
2802 f = open(diskname, "rb")
2803 data = f.read()
2804 f.close()
2805 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002806
2807 def WriteToTemp(self):
2808 t = tempfile.NamedTemporaryFile()
2809 t.write(self.data)
2810 t.flush()
2811 return t
2812
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002813 def WriteToDir(self, d):
2814 with open(os.path.join(d, self.name), "wb") as fp:
2815 fp.write(self.data)
2816
Geremy Condra36bd3652014-02-06 19:45:10 -08002817 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002818 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002819
Tao Bao76def242017-11-21 09:25:31 -08002820
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002821DIFF_PROGRAM_BY_EXT = {
Kelvin Zhang0876c412020-06-23 15:06:58 -04002822 ".gz": "imgdiff",
2823 ".zip": ["imgdiff", "-z"],
2824 ".jar": ["imgdiff", "-z"],
2825 ".apk": ["imgdiff", "-z"],
2826 ".img": "imgdiff",
2827}
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002828
Tao Bao76def242017-11-21 09:25:31 -08002829
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002830class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002831 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002832 self.tf = tf
2833 self.sf = sf
2834 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002835 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002836
2837 def ComputePatch(self):
2838 """Compute the patch (as a string of data) needed to turn sf into
2839 tf. Returns the same tuple as GetPatch()."""
2840
2841 tf = self.tf
2842 sf = self.sf
2843
Doug Zongker24cd2802012-08-14 16:36:15 -07002844 if self.diff_program:
2845 diff_program = self.diff_program
2846 else:
2847 ext = os.path.splitext(tf.name)[1]
2848 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002849
2850 ttemp = tf.WriteToTemp()
2851 stemp = sf.WriteToTemp()
2852
2853 ext = os.path.splitext(tf.name)[1]
2854
2855 try:
2856 ptemp = tempfile.NamedTemporaryFile()
2857 if isinstance(diff_program, list):
2858 cmd = copy.copy(diff_program)
2859 else:
2860 cmd = [diff_program]
2861 cmd.append(stemp.name)
2862 cmd.append(ttemp.name)
2863 cmd.append(ptemp.name)
2864 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002865 err = []
Kelvin Zhang0876c412020-06-23 15:06:58 -04002866
Doug Zongkerf8340082014-08-05 10:39:37 -07002867 def run():
2868 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002869 if e:
2870 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002871 th = threading.Thread(target=run)
2872 th.start()
2873 th.join(timeout=300) # 5 mins
2874 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002875 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002876 p.terminate()
2877 th.join(5)
2878 if th.is_alive():
2879 p.kill()
2880 th.join()
2881
Tianjie Xua2a9f992018-01-05 15:15:54 -08002882 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002883 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002884 self.patch = None
2885 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002886 diff = ptemp.read()
2887 finally:
2888 ptemp.close()
2889 stemp.close()
2890 ttemp.close()
2891
2892 self.patch = diff
2893 return self.tf, self.sf, self.patch
2894
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002895 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002896 """Returns a tuple of (target_file, source_file, patch_data).
2897
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002898 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002899 computing the patch failed.
2900 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002901 return self.tf, self.sf, self.patch
2902
2903
2904def ComputeDifferences(diffs):
2905 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002906 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002907
2908 # Do the largest files first, to try and reduce the long-pole effect.
2909 by_size = [(i.tf.size, i) for i in diffs]
2910 by_size.sort(reverse=True)
2911 by_size = [i[1] for i in by_size]
2912
2913 lock = threading.Lock()
2914 diff_iter = iter(by_size) # accessed under lock
2915
2916 def worker():
2917 try:
2918 lock.acquire()
2919 for d in diff_iter:
2920 lock.release()
2921 start = time.time()
2922 d.ComputePatch()
2923 dur = time.time() - start
2924 lock.acquire()
2925
2926 tf, sf, patch = d.GetPatch()
2927 if sf.name == tf.name:
2928 name = tf.name
2929 else:
2930 name = "%s (%s)" % (tf.name, sf.name)
2931 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002932 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002933 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002934 logger.info(
2935 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2936 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002937 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002938 except Exception:
2939 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002940 raise
2941
2942 # start worker threads; wait for them all to finish.
2943 threads = [threading.Thread(target=worker)
2944 for i in range(OPTIONS.worker_threads)]
2945 for th in threads:
2946 th.start()
2947 while threads:
2948 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002949
2950
Dan Albert8b72aef2015-03-23 19:13:21 -07002951class BlockDifference(object):
2952 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002953 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002954 self.tgt = tgt
2955 self.src = src
2956 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002957 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002958 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002959
Tao Baodd2a5892015-03-12 12:32:37 -07002960 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002961 version = max(
2962 int(i) for i in
2963 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002964 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002965 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002966
Tianjie Xu41976c72019-07-03 13:57:01 -07002967 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2968 version=self.version,
2969 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002970 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002971 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002972 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002973 self.touched_src_ranges = b.touched_src_ranges
2974 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002975
Yifan Hong10c530d2018-12-27 17:34:18 -08002976 # On devices with dynamic partitions, for new partitions,
2977 # src is None but OPTIONS.source_info_dict is not.
2978 if OPTIONS.source_info_dict is None:
2979 is_dynamic_build = OPTIONS.info_dict.get(
2980 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002981 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002982 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002983 is_dynamic_build = OPTIONS.source_info_dict.get(
2984 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002985 is_dynamic_source = partition in shlex.split(
2986 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002987
Yifan Hongbb2658d2019-01-25 12:30:58 -08002988 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002989 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2990
Yifan Hongbb2658d2019-01-25 12:30:58 -08002991 # For dynamic partitions builds, check partition list in both source
2992 # and target build because new partitions may be added, and existing
2993 # partitions may be removed.
2994 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2995
Yifan Hong10c530d2018-12-27 17:34:18 -08002996 if is_dynamic:
2997 self.device = 'map_partition("%s")' % partition
2998 else:
2999 if OPTIONS.source_info_dict is None:
Yifan Hongbdb32012020-05-07 12:38:53 -07003000 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3001 OPTIONS.info_dict)
Yifan Hong10c530d2018-12-27 17:34:18 -08003002 else:
Yifan Hongbdb32012020-05-07 12:38:53 -07003003 _, device_expr = GetTypeAndDeviceExpr("/" + partition,
3004 OPTIONS.source_info_dict)
3005 self.device = device_expr
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003006
Tao Baod8d14be2016-02-04 14:26:02 -08003007 @property
3008 def required_cache(self):
3009 return self._required_cache
3010
Tao Bao76def242017-11-21 09:25:31 -08003011 def WriteScript(self, script, output_zip, progress=None,
3012 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003013 if not self.src:
3014 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08003015 script.Print("Patching %s image unconditionally..." % (self.partition,))
3016 else:
3017 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003018
Dan Albert8b72aef2015-03-23 19:13:21 -07003019 if progress:
3020 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003021 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08003022
3023 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08003024 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08003025
Tao Bao9bc6bb22015-11-09 16:58:28 -08003026 def WriteStrictVerifyScript(self, script):
3027 """Verify all the blocks in the care_map, including clobbered blocks.
3028
3029 This differs from the WriteVerifyScript() function: a) it prints different
3030 error messages; b) it doesn't allow half-way updated images to pass the
3031 verification."""
3032
3033 partition = self.partition
3034 script.Print("Verifying %s..." % (partition,))
3035 ranges = self.tgt.care_map
3036 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003037 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003038 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
3039 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08003040 self.device, ranges_str,
3041 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08003042 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08003043 script.AppendExtra("")
3044
Tao Baod522bdc2016-04-12 15:53:16 -07003045 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00003046 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07003047
3048 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08003049 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00003050 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07003051
3052 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003053 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08003054 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07003055 ranges = self.touched_src_ranges
3056 expected_sha1 = self.touched_src_sha1
3057 else:
3058 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
3059 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07003060
3061 # No blocks to be checked, skipping.
3062 if not ranges:
3063 return
3064
Tao Bao5ece99d2015-05-12 11:42:31 -07003065 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003066 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003067 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08003068 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
3069 '"%s.patch.dat")) then' % (
3070 self.device, ranges_str, expected_sha1,
3071 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07003072 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07003073 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00003074
Tianjie Xufc3422a2015-12-15 11:53:59 -08003075 if self.version >= 4:
3076
3077 # Bug: 21124327
3078 # When generating incrementals for the system and vendor partitions in
3079 # version 4 or newer, explicitly check the first block (which contains
3080 # the superblock) of the partition to see if it's what we expect. If
3081 # this check fails, give an explicit log message about the partition
3082 # having been remounted R/W (the most likely explanation).
3083 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08003084 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08003085
3086 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07003087 if partition == "system":
3088 code = ErrorCode.SYSTEM_RECOVER_FAILURE
3089 else:
3090 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08003091 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08003092 'ifelse (block_image_recover({device}, "{ranges}") && '
3093 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08003094 'package_extract_file("{partition}.transfer.list"), '
3095 '"{partition}.new.dat", "{partition}.patch.dat"), '
3096 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07003097 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08003098 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07003099 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07003100
Tao Baodd2a5892015-03-12 12:32:37 -07003101 # Abort the OTA update. Note that the incremental OTA cannot be applied
3102 # even if it may match the checksum of the target partition.
3103 # a) If version < 3, operations like move and erase will make changes
3104 # unconditionally and damage the partition.
3105 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08003106 else:
Tianjie Xu209db462016-05-24 17:34:52 -07003107 if partition == "system":
3108 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
3109 else:
3110 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
3111 script.AppendExtra((
3112 'abort("E%d: %s partition has unexpected contents");\n'
3113 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003114
Yifan Hong10c530d2018-12-27 17:34:18 -08003115 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07003116 partition = self.partition
3117 script.Print('Verifying the updated %s image...' % (partition,))
3118 # Unlike pre-install verification, clobbered_blocks should not be ignored.
3119 ranges = self.tgt.care_map
3120 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003121 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003122 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003123 self.device, ranges_str,
3124 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07003125
3126 # Bug: 20881595
3127 # Verify that extended blocks are really zeroed out.
3128 if self.tgt.extended:
3129 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08003130 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08003131 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08003132 self.device, ranges_str,
3133 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07003134 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07003135 if partition == "system":
3136 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
3137 else:
3138 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07003139 script.AppendExtra(
3140 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003141 ' abort("E%d: %s partition has unexpected non-zero contents after '
3142 'OTA update");\n'
3143 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07003144 else:
3145 script.Print('Verified the updated %s image.' % (partition,))
3146
Tianjie Xu209db462016-05-24 17:34:52 -07003147 if partition == "system":
3148 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
3149 else:
3150 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
3151
Tao Bao5fcaaef2015-06-01 13:40:49 -07003152 script.AppendExtra(
3153 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003154 ' abort("E%d: %s partition has unexpected contents after OTA '
3155 'update");\n'
3156 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07003157
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003158 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08003159 ZipWrite(output_zip,
3160 '{}.transfer.list'.format(self.path),
3161 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003162
Tao Bao76def242017-11-21 09:25:31 -08003163 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
3164 # its size. Quailty 9 almost triples the compression time but doesn't
3165 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003166 # zip | brotli(quality 6) | brotli(quality 9)
3167 # compressed_size: 942M | 869M (~8% reduced) | 854M
3168 # compression_time: 75s | 265s | 719s
3169 # decompression_time: 15s | 25s | 25s
3170
3171 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01003172 brotli_cmd = ['brotli', '--quality=6',
3173 '--output={}.new.dat.br'.format(self.path),
3174 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003175 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07003176 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003177
3178 new_data_name = '{}.new.dat.br'.format(self.partition)
3179 ZipWrite(output_zip,
3180 '{}.new.dat.br'.format(self.path),
3181 new_data_name,
3182 compress_type=zipfile.ZIP_STORED)
3183 else:
3184 new_data_name = '{}.new.dat'.format(self.partition)
3185 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
3186
Dan Albert8e0178d2015-01-27 15:53:15 -08003187 ZipWrite(output_zip,
3188 '{}.patch.dat'.format(self.path),
3189 '{}.patch.dat'.format(self.partition),
3190 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003191
Tianjie Xu209db462016-05-24 17:34:52 -07003192 if self.partition == "system":
3193 code = ErrorCode.SYSTEM_UPDATE_FAILURE
3194 else:
3195 code = ErrorCode.VENDOR_UPDATE_FAILURE
3196
Yifan Hong10c530d2018-12-27 17:34:18 -08003197 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08003198 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003199 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07003200 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07003201 device=self.device, partition=self.partition,
3202 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07003203 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003204
Kelvin Zhang0876c412020-06-23 15:06:58 -04003205 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00003206 data = source.ReadRangeSet(ranges)
3207 ctx = sha1()
3208
3209 for p in data:
3210 ctx.update(p)
3211
3212 return ctx.hexdigest()
3213
Kelvin Zhang0876c412020-06-23 15:06:58 -04003214 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
Tao Baoe9b61912015-07-09 17:37:49 -07003215 """Return the hash value for all zero blocks."""
3216 zero_block = '\x00' * 4096
3217 ctx = sha1()
3218 for _ in range(num_blocks):
3219 ctx.update(zero_block)
3220
3221 return ctx.hexdigest()
3222
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07003223
Tianjie Xu41976c72019-07-03 13:57:01 -07003224# Expose these two classes to support vendor-specific scripts
3225DataImage = images.DataImage
3226EmptyImage = images.EmptyImage
3227
Tao Bao76def242017-11-21 09:25:31 -08003228
Doug Zongker96a57e72010-09-26 14:57:41 -07003229# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07003230PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07003231 "ext4": "EMMC",
3232 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07003233 "f2fs": "EMMC",
3234 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07003235}
Doug Zongker96a57e72010-09-26 14:57:41 -07003236
Kelvin Zhang0876c412020-06-23 15:06:58 -04003237
Yifan Hongbdb32012020-05-07 12:38:53 -07003238def GetTypeAndDevice(mount_point, info, check_no_slot=True):
3239 """
3240 Use GetTypeAndDeviceExpr whenever possible. This function is kept for
3241 backwards compatibility. It aborts if the fstab entry has slotselect option
3242 (unless check_no_slot is explicitly set to False).
3243 """
Doug Zongker96a57e72010-09-26 14:57:41 -07003244 fstab = info["fstab"]
3245 if fstab:
Yifan Hongbdb32012020-05-07 12:38:53 -07003246 if check_no_slot:
3247 assert not fstab[mount_point].slotselect, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04003248 "Use GetTypeAndDeviceExpr instead"
Dan Albert8b72aef2015-03-23 19:13:21 -07003249 return (PARTITION_TYPES[fstab[mount_point].fs_type],
3250 fstab[mount_point].device)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003251 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003252
3253
Yifan Hongbdb32012020-05-07 12:38:53 -07003254def GetTypeAndDeviceExpr(mount_point, info):
3255 """
3256 Return the filesystem of the partition, and an edify expression that evaluates
3257 to the device at runtime.
3258 """
3259 fstab = info["fstab"]
3260 if fstab:
3261 p = fstab[mount_point]
3262 device_expr = '"%s"' % fstab[mount_point].device
3263 if p.slotselect:
3264 device_expr = 'add_slot_suffix(%s)' % device_expr
3265 return (PARTITION_TYPES[fstab[mount_point].fs_type], device_expr)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003266 raise KeyError
Yifan Hongbdb32012020-05-07 12:38:53 -07003267
3268
3269def GetEntryForDevice(fstab, device):
3270 """
3271 Returns:
3272 The first entry in fstab whose device is the given value.
3273 """
3274 if not fstab:
3275 return None
3276 for mount_point in fstab:
3277 if fstab[mount_point].device == device:
3278 return fstab[mount_point]
3279 return None
3280
Kelvin Zhang0876c412020-06-23 15:06:58 -04003281
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003282def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08003283 """Parses and converts a PEM-encoded certificate into DER-encoded.
3284
3285 This gives the same result as `openssl x509 -in <filename> -outform DER`.
3286
3287 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08003288 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08003289 """
3290 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003291 save = False
3292 for line in data.split("\n"):
3293 if "--END CERTIFICATE--" in line:
3294 break
3295 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08003296 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003297 if "--BEGIN CERTIFICATE--" in line:
3298 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08003299 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00003300 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08003301
Tao Bao04e1f012018-02-04 12:13:35 -08003302
3303def ExtractPublicKey(cert):
3304 """Extracts the public key (PEM-encoded) from the given certificate file.
3305
3306 Args:
3307 cert: The certificate filename.
3308
3309 Returns:
3310 The public key string.
3311
3312 Raises:
3313 AssertionError: On non-zero return from 'openssl'.
3314 """
3315 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
3316 # While openssl 1.1 writes the key into the given filename followed by '-out',
3317 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
3318 # stdout instead.
3319 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
3320 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
3321 pubkey, stderrdata = proc.communicate()
3322 assert proc.returncode == 0, \
3323 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
3324 return pubkey
3325
3326
Tao Bao1ac886e2019-06-26 11:58:22 -07003327def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07003328 """Extracts the AVB public key from the given public or private key.
3329
3330 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07003331 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07003332 key: The input key file, which should be PEM-encoded public or private key.
3333
3334 Returns:
3335 The path to the extracted AVB public key file.
3336 """
3337 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
3338 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07003339 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07003340 return output
3341
3342
Doug Zongker412c02f2014-02-13 10:58:24 -08003343def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
3344 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08003345 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08003346
Tao Bao6d5d6232018-03-09 17:04:42 -08003347 Most of the space in the boot and recovery images is just the kernel, which is
3348 identical for the two, so the resulting patch should be efficient. Add it to
3349 the output zip, along with a shell script that is run from init.rc on first
3350 boot to actually do the patching and install the new recovery image.
3351
3352 Args:
3353 input_dir: The top-level input directory of the target-files.zip.
3354 output_sink: The callback function that writes the result.
3355 recovery_img: File object for the recovery image.
3356 boot_img: File objects for the boot image.
3357 info_dict: A dict returned by common.LoadInfoDict() on the input
3358 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08003359 """
Doug Zongker412c02f2014-02-13 10:58:24 -08003360 if info_dict is None:
3361 info_dict = OPTIONS.info_dict
3362
Tao Bao6d5d6232018-03-09 17:04:42 -08003363 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003364 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
3365
3366 if board_uses_vendorimage:
3367 # In this case, the output sink is rooted at VENDOR
3368 recovery_img_path = "etc/recovery.img"
3369 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
3370 sh_dir = "bin"
3371 else:
3372 # In this case the output sink is rooted at SYSTEM
3373 recovery_img_path = "vendor/etc/recovery.img"
3374 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
3375 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08003376
Tao Baof2cffbd2015-07-22 12:33:18 -07003377 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003378 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07003379
3380 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08003381 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07003382 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08003383 # With system-root-image, boot and recovery images will have mismatching
3384 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
3385 # to handle such a case.
3386 if system_root_image:
3387 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07003388 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08003389 assert not os.path.exists(path)
3390 else:
3391 diff_program = ["imgdiff"]
3392 if os.path.exists(path):
3393 diff_program.append("-b")
3394 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07003395 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08003396 else:
3397 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07003398
3399 d = Difference(recovery_img, boot_img, diff_program=diff_program)
3400 _, _, patch = d.ComputePatch()
3401 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08003402
Dan Albertebb19aa2015-03-27 19:11:53 -07003403 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07003404 # The following GetTypeAndDevice()s need to use the path in the target
3405 # info_dict instead of source_info_dict.
Yifan Hongbdb32012020-05-07 12:38:53 -07003406 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict,
3407 check_no_slot=False)
3408 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict,
3409 check_no_slot=False)
Dan Albertebb19aa2015-03-27 19:11:53 -07003410 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07003411 return
Doug Zongkerc9253822014-02-04 12:17:58 -08003412
Tao Baof2cffbd2015-07-22 12:33:18 -07003413 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07003414
3415 # Note that we use /vendor to refer to the recovery resources. This will
3416 # work for a separate vendor partition mounted at /vendor or a
3417 # /system/vendor subdirectory on the system partition, for which init will
3418 # create a symlink from /vendor to /system/vendor.
3419
3420 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003421if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
3422 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003423 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07003424 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
3425 log -t recovery "Installing new recovery image: succeeded" || \\
3426 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07003427else
3428 log -t recovery "Recovery image already installed"
3429fi
3430""" % {'type': recovery_type,
3431 'device': recovery_device,
3432 'sha1': recovery_img.sha1,
3433 'size': recovery_img.size}
3434 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07003435 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07003436if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
3437 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07003438 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07003439 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
3440 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
3441 log -t recovery "Installing new recovery image: succeeded" || \\
3442 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08003443else
3444 log -t recovery "Recovery image already installed"
3445fi
Dan Albert8b72aef2015-03-23 19:13:21 -07003446""" % {'boot_size': boot_img.size,
3447 'boot_sha1': boot_img.sha1,
3448 'recovery_size': recovery_img.size,
3449 'recovery_sha1': recovery_img.sha1,
3450 'boot_type': boot_type,
Yifan Hongbdb32012020-05-07 12:38:53 -07003451 'boot_device': boot_device + '$(getprop ro.boot.slot_suffix)',
Tianjiee3c31ea2020-05-19 13:44:26 -07003452 'recovery_type': recovery_type,
3453 'recovery_device': recovery_device + '$(getprop ro.boot.slot_suffix)',
Dan Albert8b72aef2015-03-23 19:13:21 -07003454 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08003455
Bill Peckhame868aec2019-09-17 17:06:47 -07003456 # The install script location moved from /system/etc to /system/bin in the L
3457 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
3458 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07003459
Tao Bao32fcdab2018-10-12 10:30:39 -07003460 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08003461
Tao Baoda30cfa2017-12-01 16:19:46 -08003462 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08003463
3464
3465class DynamicPartitionUpdate(object):
3466 def __init__(self, src_group=None, tgt_group=None, progress=None,
3467 block_difference=None):
3468 self.src_group = src_group
3469 self.tgt_group = tgt_group
3470 self.progress = progress
3471 self.block_difference = block_difference
3472
3473 @property
3474 def src_size(self):
3475 if not self.block_difference:
3476 return 0
3477 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
3478
3479 @property
3480 def tgt_size(self):
3481 if not self.block_difference:
3482 return 0
3483 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
3484
3485 @staticmethod
3486 def _GetSparseImageSize(img):
3487 if not img:
3488 return 0
3489 return img.blocksize * img.total_blocks
3490
3491
3492class DynamicGroupUpdate(object):
3493 def __init__(self, src_size=None, tgt_size=None):
3494 # None: group does not exist. 0: no size limits.
3495 self.src_size = src_size
3496 self.tgt_size = tgt_size
3497
3498
3499class DynamicPartitionsDifference(object):
3500 def __init__(self, info_dict, block_diffs, progress_dict=None,
3501 source_info_dict=None):
3502 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07003503 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003504
3505 self._remove_all_before_apply = False
3506 if source_info_dict is None:
3507 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07003508 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08003509
Tao Baof1113e92019-06-18 12:10:14 -07003510 block_diff_dict = collections.OrderedDict(
3511 [(e.partition, e) for e in block_diffs])
3512
Yifan Hong10c530d2018-12-27 17:34:18 -08003513 assert len(block_diff_dict) == len(block_diffs), \
3514 "Duplicated BlockDifference object for {}".format(
3515 [partition for partition, count in
3516 collections.Counter(e.partition for e in block_diffs).items()
3517 if count > 1])
3518
Yifan Hong79997e52019-01-23 16:56:19 -08003519 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003520
3521 for p, block_diff in block_diff_dict.items():
3522 self._partition_updates[p] = DynamicPartitionUpdate()
3523 self._partition_updates[p].block_difference = block_diff
3524
3525 for p, progress in progress_dict.items():
3526 if p in self._partition_updates:
3527 self._partition_updates[p].progress = progress
3528
3529 tgt_groups = shlex.split(info_dict.get(
3530 "super_partition_groups", "").strip())
3531 src_groups = shlex.split(source_info_dict.get(
3532 "super_partition_groups", "").strip())
3533
3534 for g in tgt_groups:
3535 for p in shlex.split(info_dict.get(
Daniel Norman2d7989a2021-04-05 17:40:47 +00003536 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003537 assert p in self._partition_updates, \
3538 "{} is in target super_{}_partition_list but no BlockDifference " \
3539 "object is provided.".format(p, g)
3540 self._partition_updates[p].tgt_group = g
3541
3542 for g in src_groups:
3543 for p in shlex.split(source_info_dict.get(
Daniel Norman2d7989a2021-04-05 17:40:47 +00003544 "super_%s_partition_list" % g, "").strip()):
Yifan Hong10c530d2018-12-27 17:34:18 -08003545 assert p in self._partition_updates, \
3546 "{} is in source super_{}_partition_list but no BlockDifference " \
3547 "object is provided.".format(p, g)
3548 self._partition_updates[p].src_group = g
3549
Yifan Hong45433e42019-01-18 13:55:25 -08003550 target_dynamic_partitions = set(shlex.split(info_dict.get(
3551 "dynamic_partition_list", "").strip()))
3552 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
3553 if u.tgt_size)
3554 assert block_diffs_with_target == target_dynamic_partitions, \
3555 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
3556 list(target_dynamic_partitions), list(block_diffs_with_target))
3557
3558 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
3559 "dynamic_partition_list", "").strip()))
3560 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
3561 if u.src_size)
3562 assert block_diffs_with_source == source_dynamic_partitions, \
3563 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
3564 list(source_dynamic_partitions), list(block_diffs_with_source))
3565
Yifan Hong10c530d2018-12-27 17:34:18 -08003566 if self._partition_updates:
3567 logger.info("Updating dynamic partitions %s",
3568 self._partition_updates.keys())
3569
Yifan Hong79997e52019-01-23 16:56:19 -08003570 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08003571
3572 for g in tgt_groups:
3573 self._group_updates[g] = DynamicGroupUpdate()
3574 self._group_updates[g].tgt_size = int(info_dict.get(
3575 "super_%s_group_size" % g, "0").strip())
3576
3577 for g in src_groups:
3578 if g not in self._group_updates:
3579 self._group_updates[g] = DynamicGroupUpdate()
3580 self._group_updates[g].src_size = int(source_info_dict.get(
3581 "super_%s_group_size" % g, "0").strip())
3582
3583 self._Compute()
3584
3585 def WriteScript(self, script, output_zip, write_verify_script=False):
3586 script.Comment('--- Start patching dynamic partitions ---')
3587 for p, u in self._partition_updates.items():
3588 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3589 script.Comment('Patch partition %s' % p)
3590 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3591 write_verify_script=False)
3592
3593 op_list_path = MakeTempFile()
3594 with open(op_list_path, 'w') as f:
3595 for line in self._op_list:
3596 f.write('{}\n'.format(line))
3597
3598 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3599
3600 script.Comment('Update dynamic partition metadata')
3601 script.AppendExtra('assert(update_dynamic_partitions('
3602 'package_extract_file("dynamic_partitions_op_list")));')
3603
3604 if write_verify_script:
3605 for p, u in self._partition_updates.items():
3606 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3607 u.block_difference.WritePostInstallVerifyScript(script)
Kelvin Zhang0876c412020-06-23 15:06:58 -04003608 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003609
3610 for p, u in self._partition_updates.items():
3611 if u.tgt_size and u.src_size <= u.tgt_size:
3612 script.Comment('Patch partition %s' % p)
3613 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3614 write_verify_script=write_verify_script)
3615 if write_verify_script:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003616 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
Yifan Hong10c530d2018-12-27 17:34:18 -08003617
3618 script.Comment('--- End patching dynamic partitions ---')
3619
3620 def _Compute(self):
3621 self._op_list = list()
3622
3623 def append(line):
3624 self._op_list.append(line)
3625
3626 def comment(line):
3627 self._op_list.append("# %s" % line)
3628
3629 if self._remove_all_before_apply:
3630 comment('Remove all existing dynamic partitions and groups before '
3631 'applying full OTA')
3632 append('remove_all_groups')
3633
3634 for p, u in self._partition_updates.items():
3635 if u.src_group and not u.tgt_group:
3636 append('remove %s' % p)
3637
3638 for p, u in self._partition_updates.items():
3639 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3640 comment('Move partition %s from %s to default' % (p, u.src_group))
3641 append('move %s default' % p)
3642
3643 for p, u in self._partition_updates.items():
3644 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3645 comment('Shrink partition %s from %d to %d' %
3646 (p, u.src_size, u.tgt_size))
3647 append('resize %s %s' % (p, u.tgt_size))
3648
3649 for g, u in self._group_updates.items():
3650 if u.src_size is not None and u.tgt_size is None:
3651 append('remove_group %s' % g)
3652 if (u.src_size is not None and u.tgt_size is not None and
Daniel Norman2d7989a2021-04-05 17:40:47 +00003653 u.src_size > u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003654 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3655 append('resize_group %s %d' % (g, u.tgt_size))
3656
3657 for g, u in self._group_updates.items():
3658 if u.src_size is None and u.tgt_size is not None:
3659 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3660 append('add_group %s %d' % (g, u.tgt_size))
3661 if (u.src_size is not None and u.tgt_size is not None and
Daniel Norman2d7989a2021-04-05 17:40:47 +00003662 u.src_size < u.tgt_size):
Yifan Hong10c530d2018-12-27 17:34:18 -08003663 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3664 append('resize_group %s %d' % (g, u.tgt_size))
3665
3666 for p, u in self._partition_updates.items():
3667 if u.tgt_group and not u.src_group:
3668 comment('Add partition %s to group %s' % (p, u.tgt_group))
3669 append('add %s %s' % (p, u.tgt_group))
3670
3671 for p, u in self._partition_updates.items():
3672 if u.tgt_size and u.src_size < u.tgt_size:
Kelvin Zhang0876c412020-06-23 15:06:58 -04003673 comment('Grow partition %s from %d to %d' %
3674 (p, u.src_size, u.tgt_size))
Yifan Hong10c530d2018-12-27 17:34:18 -08003675 append('resize %s %d' % (p, u.tgt_size))
3676
3677 for p, u in self._partition_updates.items():
3678 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3679 comment('Move partition %s from default to %s' %
3680 (p, u.tgt_group))
3681 append('move %s %s' % (p, u.tgt_group))
Yifan Hongc65a0542021-01-07 14:21:01 -08003682
3683
jiajia tangf3f842b2021-03-17 21:49:44 +08003684def GetBootImageBuildProp(boot_img, ramdisk_format=RamdiskFormat.LZ4):
Yifan Hongc65a0542021-01-07 14:21:01 -08003685 """
Yifan Hong85ac5012021-01-07 14:43:46 -08003686 Get build.prop from ramdisk within the boot image
Yifan Hongc65a0542021-01-07 14:21:01 -08003687
3688 Args:
jiajia tangf3f842b2021-03-17 21:49:44 +08003689 boot_img: the boot image file. Ramdisk must be compressed with lz4 or minigzip format.
Yifan Hongc65a0542021-01-07 14:21:01 -08003690
3691 Return:
Yifan Hong85ac5012021-01-07 14:43:46 -08003692 An extracted file that stores properties in the boot image.
Yifan Hongc65a0542021-01-07 14:21:01 -08003693 """
Yifan Hongc65a0542021-01-07 14:21:01 -08003694 tmp_dir = MakeTempDir('boot_', suffix='.img')
3695 try:
Daniel Norman2d7989a2021-04-05 17:40:47 +00003696 RunAndCheckOutput(['unpack_bootimg', '--boot_img', boot_img, '--out', tmp_dir])
Yifan Hongc65a0542021-01-07 14:21:01 -08003697 ramdisk = os.path.join(tmp_dir, 'ramdisk')
3698 if not os.path.isfile(ramdisk):
3699 logger.warning('Unable to get boot image timestamp: no ramdisk in boot')
3700 return None
3701 uncompressed_ramdisk = os.path.join(tmp_dir, 'uncompressed_ramdisk')
jiajia tangf3f842b2021-03-17 21:49:44 +08003702 if ramdisk_format == RamdiskFormat.LZ4:
3703 RunAndCheckOutput(['lz4', '-d', ramdisk, uncompressed_ramdisk])
3704 elif ramdisk_format == RamdiskFormat.GZ:
3705 with open(ramdisk, 'rb') as input_stream:
3706 with open(uncompressed_ramdisk, 'wb') as output_stream:
3707 p2 = Run(['minigzip', '-d'], stdin=input_stream.fileno(), stdout=output_stream.fileno())
3708 p2.wait()
3709 else:
3710 logger.error('Only support lz4 or minigzip ramdisk format.')
3711 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003712
3713 abs_uncompressed_ramdisk = os.path.abspath(uncompressed_ramdisk)
3714 extracted_ramdisk = MakeTempDir('extracted_ramdisk')
3715 # Use "toybox cpio" instead of "cpio" because the latter invokes cpio from
3716 # the host environment.
3717 RunAndCheckOutput(['toybox', 'cpio', '-F', abs_uncompressed_ramdisk, '-i'],
Daniel Norman2d7989a2021-04-05 17:40:47 +00003718 cwd=extracted_ramdisk)
Yifan Hongc65a0542021-01-07 14:21:01 -08003719
Yifan Hongc65a0542021-01-07 14:21:01 -08003720 for search_path in RAMDISK_BUILD_PROP_REL_PATHS:
3721 prop_file = os.path.join(extracted_ramdisk, search_path)
3722 if os.path.isfile(prop_file):
Yifan Hong7dc51172021-01-12 11:27:39 -08003723 return prop_file
Daniel Norman2d7989a2021-04-05 17:40:47 +00003724 logger.warning('Unable to get boot image timestamp: no %s in ramdisk', search_path)
Yifan Hongc65a0542021-01-07 14:21:01 -08003725
Yifan Hong7dc51172021-01-12 11:27:39 -08003726 return None
Yifan Hongc65a0542021-01-07 14:21:01 -08003727
Yifan Hong85ac5012021-01-07 14:43:46 -08003728 except ExternalError as e:
3729 logger.warning('Unable to get boot image build props: %s', e)
3730 return None
3731
3732
3733def GetBootImageTimestamp(boot_img):
3734 """
3735 Get timestamp from ramdisk within the boot image
3736
3737 Args:
3738 boot_img: the boot image file. Ramdisk must be compressed with lz4 format.
3739
3740 Return:
3741 An integer that corresponds to the timestamp of the boot image, or None
3742 if file has unknown format. Raise exception if an unexpected error has
3743 occurred.
3744 """
3745 prop_file = GetBootImageBuildProp(boot_img)
3746 if not prop_file:
3747 return None
3748
3749 props = PartitionBuildProps.FromBuildPropFile('boot', prop_file)
3750 if props is None:
3751 return None
3752
3753 try:
Yifan Hongc65a0542021-01-07 14:21:01 -08003754 timestamp = props.GetProp('ro.bootimage.build.date.utc')
3755 if timestamp:
3756 return int(timestamp)
Daniel Norman2d7989a2021-04-05 17:40:47 +00003757 logger.warning('Unable to get boot image timestamp: ro.bootimage.build.date.utc is undefined')
Yifan Hongc65a0542021-01-07 14:21:01 -08003758 return None
3759
3760 except ExternalError as e:
3761 logger.warning('Unable to get boot image timestamp: %s', e)
3762 return None
Kelvin Zhang27324132021-03-22 15:38:38 -04003763
3764
3765def GetCareMap(which, imgname):
3766 """Returns the care_map string for the given partition.
3767
3768 Args:
3769 which: The partition name, must be listed in PARTITIONS_WITH_CARE_MAP.
3770 imgname: The filename of the image.
3771
3772 Returns:
3773 (which, care_map_ranges): care_map_ranges is the raw string of the care_map
3774 RangeSet; or None.
3775 """
3776 assert which in PARTITIONS_WITH_CARE_MAP
3777
3778 # which + "_image_size" contains the size that the actual filesystem image
3779 # resides in, which is all that needs to be verified. The additional blocks in
3780 # the image file contain verity metadata, by reading which would trigger
3781 # invalid reads.
3782 image_size = OPTIONS.info_dict.get(which + "_image_size")
3783 if not image_size:
3784 return None
3785
3786 image_blocks = int(image_size) // 4096 - 1
3787 assert image_blocks > 0, "blocks for {} must be positive".format(which)
3788
3789 # For sparse images, we will only check the blocks that are listed in the care
3790 # map, i.e. the ones with meaningful data.
3791 if "extfs_sparse_flag" in OPTIONS.info_dict:
3792 simg = sparse_img.SparseImage(imgname)
3793 care_map_ranges = simg.care_map.intersect(
3794 rangelib.RangeSet("0-{}".format(image_blocks)))
3795
3796 # Otherwise for non-sparse images, we read all the blocks in the filesystem
3797 # image.
3798 else:
3799 care_map_ranges = rangelib.RangeSet("0-{}".format(image_blocks))
3800
3801 return [which, care_map_ranges.to_string_raw()]
3802
3803
3804def AddCareMapForAbOta(output_file, ab_partitions, image_paths):
3805 """Generates and adds care_map.pb for a/b partition that has care_map.
3806
3807 Args:
3808 output_file: The output zip file (needs to be already open),
3809 or file path to write care_map.pb.
3810 ab_partitions: The list of A/B partitions.
3811 image_paths: A map from the partition name to the image path.
3812 """
3813 if not output_file:
3814 raise ExternalError('Expected output_file for AddCareMapForAbOta')
3815
3816 care_map_list = []
3817 for partition in ab_partitions:
3818 partition = partition.strip()
3819 if partition not in PARTITIONS_WITH_CARE_MAP:
3820 continue
3821
3822 verity_block_device = "{}_verity_block_device".format(partition)
3823 avb_hashtree_enable = "avb_{}_hashtree_enable".format(partition)
3824 if (verity_block_device in OPTIONS.info_dict or
3825 OPTIONS.info_dict.get(avb_hashtree_enable) == "true"):
3826 if partition not in image_paths:
3827 logger.warning('Potential partition with care_map missing from images: %s',
3828 partition)
3829 continue
3830 image_path = image_paths[partition]
3831 if not os.path.exists(image_path):
3832 raise ExternalError('Expected image at path {}'.format(image_path))
3833
3834 care_map = GetCareMap(partition, image_path)
3835 if not care_map:
3836 continue
3837 care_map_list += care_map
3838
3839 # adds fingerprint field to the care_map
3840 # TODO(xunchang) revisit the fingerprint calculation for care_map.
3841 partition_props = OPTIONS.info_dict.get(partition + ".build.prop")
3842 prop_name_list = ["ro.{}.build.fingerprint".format(partition),
3843 "ro.{}.build.thumbprint".format(partition)]
3844
3845 present_props = [x for x in prop_name_list if
3846 partition_props and partition_props.GetProp(x)]
3847 if not present_props:
3848 logger.warning(
3849 "fingerprint is not present for partition %s", partition)
3850 property_id, fingerprint = "unknown", "unknown"
3851 else:
3852 property_id = present_props[0]
3853 fingerprint = partition_props.GetProp(property_id)
3854 care_map_list += [property_id, fingerprint]
3855
3856 if not care_map_list:
3857 return
3858
3859 # Converts the list into proto buf message by calling care_map_generator; and
3860 # writes the result to a temp file.
3861 temp_care_map_text = MakeTempFile(prefix="caremap_text-",
3862 suffix=".txt")
3863 with open(temp_care_map_text, 'w') as text_file:
3864 text_file.write('\n'.join(care_map_list))
3865
3866 temp_care_map = MakeTempFile(prefix="caremap-", suffix=".pb")
3867 care_map_gen_cmd = ["care_map_generator", temp_care_map_text, temp_care_map]
3868 RunAndCheckOutput(care_map_gen_cmd)
3869
3870 if not isinstance(output_file, zipfile.ZipFile):
3871 shutil.copy(temp_care_map, output_file)
3872 return
3873 # output_file is a zip file
3874 care_map_path = "META/care_map.pb"
3875 if care_map_path in output_file.namelist():
3876 # Copy the temp file into the OPTIONS.input_tmp dir and update the
3877 # replace_updated_files_list used by add_img_to_target_files
3878 if not OPTIONS.replace_updated_files_list:
3879 OPTIONS.replace_updated_files_list = []
3880 shutil.copy(temp_care_map, os.path.join(OPTIONS.input_tmp, care_map_path))
3881 OPTIONS.replace_updated_files_list.append(care_map_path)
3882 else:
3883 ZipWrite(output_file, temp_care_map, arcname=care_map_path)