blob: 04c721d22416a3e32bec18fbd8069c43482f6ecb [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070020import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070021import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070022import getopt
23import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010024import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070025import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070026import json
27import logging
28import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070029import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080030import platform
Doug Zongkereef39442009-04-02 12:14:19 -070031import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070032import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070033import shutil
34import subprocess
35import sys
36import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070037import threading
38import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070039import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080040from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070041
Tianjie Xu41976c72019-07-03 13:57:01 -070042import images
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070044from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070045
Tao Bao32fcdab2018-10-12 10:30:39 -070046logger = logging.getLogger(__name__)
47
Tao Bao986ee862018-10-04 15:46:16 -070048
Dan Albert8b72aef2015-03-23 19:13:21 -070049class Options(object):
50 def __init__(self):
Pavel Salomatov32676552019-03-06 20:00:45 +030051 base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
52 if base_out_path is None:
53 base_search_path = "out"
54 else:
Tao Bao2cc0ca12019-03-15 10:44:43 -070055 base_search_path = os.path.join(base_out_path,
56 os.path.basename(os.getcwd()))
Pavel Salomatov32676552019-03-06 20:00:45 +030057
Tao Baoa3705452019-06-24 15:33:41 -070058 # Python >= 3.3 returns 'linux', whereas Python 2.7 gives 'linux2'.
Dan Albert8b72aef2015-03-23 19:13:21 -070059 platform_search_path = {
Tao Baoa3705452019-06-24 15:33:41 -070060 "linux": os.path.join(base_search_path, "host/linux-x86"),
Pavel Salomatov32676552019-03-06 20:00:45 +030061 "linux2": os.path.join(base_search_path, "host/linux-x86"),
62 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070063 }
Doug Zongker85448772014-09-09 14:59:20 -070064
Tao Bao76def242017-11-21 09:25:31 -080065 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070066 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080067 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070068 self.extra_signapk_args = []
69 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080070 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.public_key_suffix = ".x509.pem"
72 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070073 # use otatools built boot_signer by default
74 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070075 self.boot_signer_args = []
76 self.verity_signer_path = None
77 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070078 self.verbose = False
79 self.tempfiles = []
80 self.device_specific = None
81 self.extras = {}
82 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070083 self.source_info_dict = None
84 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070085 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070086 # Stash size cannot exceed cache_size * threshold.
87 self.cache_size = None
88 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070089
90
91OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070092
Tao Bao71197512018-10-11 14:08:45 -070093# The block size that's used across the releasetools scripts.
94BLOCK_SIZE = 4096
95
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080096# Values for "certificate" in apkcerts that mean special things.
97SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
98
Tao Bao5cc0abb2019-03-21 10:18:05 -070099# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
100# that system_other is not in the list because we don't want to include its
101# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900102AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Steve Mucklee1b10862019-07-10 10:49:37 -0700103 'system_ext', 'vendor', 'vendor_boot')
Tao Bao9dd909e2017-11-14 11:27:32 -0800104
Tao Bao08c190f2019-06-03 23:07:58 -0700105# Chained VBMeta partitions.
106AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
107
Tianjie Xu861f4132018-09-12 11:49:33 -0700108# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900109PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700110
111
Tianjie Xu209db462016-05-24 17:34:52 -0700112class ErrorCode(object):
113 """Define error_codes for failures that happen during the actual
114 update package installation.
115
116 Error codes 0-999 are reserved for failures before the package
117 installation (i.e. low battery, package verification failure).
118 Detailed code in 'bootable/recovery/error_code.h' """
119
120 SYSTEM_VERIFICATION_FAILURE = 1000
121 SYSTEM_UPDATE_FAILURE = 1001
122 SYSTEM_UNEXPECTED_CONTENTS = 1002
123 SYSTEM_NONZERO_CONTENTS = 1003
124 SYSTEM_RECOVER_FAILURE = 1004
125 VENDOR_VERIFICATION_FAILURE = 2000
126 VENDOR_UPDATE_FAILURE = 2001
127 VENDOR_UNEXPECTED_CONTENTS = 2002
128 VENDOR_NONZERO_CONTENTS = 2003
129 VENDOR_RECOVER_FAILURE = 2004
130 OEM_PROP_MISMATCH = 3000
131 FINGERPRINT_MISMATCH = 3001
132 THUMBPRINT_MISMATCH = 3002
133 OLDER_BUILD = 3003
134 DEVICE_MISMATCH = 3004
135 BAD_PATCH_FILE = 3005
136 INSUFFICIENT_CACHE_SPACE = 3006
137 TUNE_PARTITION_FAILURE = 3007
138 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800139
Tao Bao80921982018-03-21 21:02:19 -0700140
Dan Albert8b72aef2015-03-23 19:13:21 -0700141class ExternalError(RuntimeError):
142 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700143
144
Tao Bao32fcdab2018-10-12 10:30:39 -0700145def InitLogging():
146 DEFAULT_LOGGING_CONFIG = {
147 'version': 1,
148 'disable_existing_loggers': False,
149 'formatters': {
150 'standard': {
151 'format':
152 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
153 'datefmt': '%Y-%m-%d %H:%M:%S',
154 },
155 },
156 'handlers': {
157 'default': {
158 'class': 'logging.StreamHandler',
159 'formatter': 'standard',
160 },
161 },
162 'loggers': {
163 '': {
164 'handlers': ['default'],
165 'level': 'WARNING',
166 'propagate': True,
167 }
168 }
169 }
170 env_config = os.getenv('LOGGING_CONFIG')
171 if env_config:
172 with open(env_config) as f:
173 config = json.load(f)
174 else:
175 config = DEFAULT_LOGGING_CONFIG
176
177 # Increase the logging level for verbose mode.
178 if OPTIONS.verbose:
179 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
180 config['loggers']['']['level'] = 'INFO'
181
182 logging.config.dictConfig(config)
183
184
Tao Bao39451582017-05-04 11:10:47 -0700185def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700186 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700187
Tao Bao73dd4f42018-10-04 16:25:33 -0700188 Args:
189 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700190 verbose: Whether the commands should be shown. Default to the global
191 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700192 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
193 stdin, etc. stdout and stderr will default to subprocess.PIPE and
194 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800195 universal_newlines will default to True, as most of the users in
196 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700197
198 Returns:
199 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700200 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700201 if 'stdout' not in kwargs and 'stderr' not in kwargs:
202 kwargs['stdout'] = subprocess.PIPE
203 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800204 if 'universal_newlines' not in kwargs:
205 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700206 # Don't log any if caller explicitly says so.
207 if verbose != False:
208 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700209 return subprocess.Popen(args, **kwargs)
210
211
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800212def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800213 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800214
215 Args:
216 args: The command represented as a list of strings.
217 verbose: Whether the commands should be shown. Default to the global
218 verbosity if unspecified.
219 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
220 stdin, etc. stdout and stderr will default to subprocess.PIPE and
221 subprocess.STDOUT respectively unless caller specifies any of them.
222
Bill Peckham889b0c62019-02-21 18:53:37 -0800223 Raises:
224 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800225 """
226 proc = Run(args, verbose=verbose, **kwargs)
227 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800228
229 if proc.returncode != 0:
230 raise ExternalError(
231 "Failed to run command '{}' (exit code {})".format(
232 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800233
234
Tao Bao986ee862018-10-04 15:46:16 -0700235def RunAndCheckOutput(args, verbose=None, **kwargs):
236 """Runs the given command and returns the output.
237
238 Args:
239 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700240 verbose: Whether the commands should be shown. Default to the global
241 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700242 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
243 stdin, etc. stdout and stderr will default to subprocess.PIPE and
244 subprocess.STDOUT respectively unless caller specifies any of them.
245
246 Returns:
247 The output string.
248
249 Raises:
250 ExternalError: On non-zero exit from the command.
251 """
Tao Bao986ee862018-10-04 15:46:16 -0700252 proc = Run(args, verbose=verbose, **kwargs)
253 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800254 if output is None:
255 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700256 # Don't log any if caller explicitly says so.
257 if verbose != False:
258 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700259 if proc.returncode != 0:
260 raise ExternalError(
261 "Failed to run command '{}' (exit code {}):\n{}".format(
262 args, proc.returncode, output))
263 return output
264
265
Tao Baoc765cca2018-01-31 17:32:40 -0800266def RoundUpTo4K(value):
267 rounded_up = value + 4095
268 return rounded_up - (rounded_up % 4096)
269
270
Ying Wang7e6d4e42010-12-13 16:25:36 -0800271def CloseInheritedPipes():
272 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
273 before doing other work."""
274 if platform.system() != "Darwin":
275 return
276 for d in range(3, 1025):
277 try:
278 stat = os.fstat(d)
279 if stat is not None:
280 pipebit = stat[0] & 0x1000
281 if pipebit != 0:
282 os.close(d)
283 except OSError:
284 pass
285
286
Tao Bao410ad8b2018-08-24 12:08:38 -0700287def LoadInfoDict(input_file, repacking=False):
288 """Loads the key/value pairs from the given input target_files.
289
290 It reads `META/misc_info.txt` file in the target_files input, does sanity
291 checks and returns the parsed key/value pairs for to the given build. It's
292 usually called early when working on input target_files files, e.g. when
293 generating OTAs, or signing builds. Note that the function may be called
294 against an old target_files file (i.e. from past dessert releases). So the
295 property parsing needs to be backward compatible.
296
297 In a `META/misc_info.txt`, a few properties are stored as links to the files
298 in the PRODUCT_OUT directory. It works fine with the build system. However,
299 they are no longer available when (re)generating images from target_files zip.
300 When `repacking` is True, redirect these properties to the actual files in the
301 unzipped directory.
302
303 Args:
304 input_file: The input target_files file, which could be an open
305 zipfile.ZipFile instance, or a str for the dir that contains the files
306 unzipped from a target_files file.
307 repacking: Whether it's trying repack an target_files file after loading the
308 info dict (default: False). If so, it will rewrite a few loaded
309 properties (e.g. selinux_fc, root_dir) to point to the actual files in
310 target_files file. When doing repacking, `input_file` must be a dir.
311
312 Returns:
313 A dict that contains the parsed key/value pairs.
314
315 Raises:
316 AssertionError: On invalid input arguments.
317 ValueError: On malformed input values.
318 """
319 if repacking:
320 assert isinstance(input_file, str), \
321 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700322
Doug Zongkerc9253822014-02-04 12:17:58 -0800323 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700324 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800325 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800326 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700327 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800328 try:
329 with open(path) as f:
330 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700331 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800332 if e.errno == errno.ENOENT:
333 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800334
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700335 try:
Michael Runge6e836112014-04-15 17:40:21 -0700336 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700337 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700338 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700339
Tao Bao410ad8b2018-08-24 12:08:38 -0700340 if "recovery_api_version" not in d:
341 raise ValueError("Failed to find 'recovery_api_version'")
342 if "fstab_version" not in d:
343 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800344
Tao Bao410ad8b2018-08-24 12:08:38 -0700345 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700346 # "selinux_fc" properties should point to the file_contexts files
347 # (file_contexts.bin) under META/.
348 for key in d:
349 if key.endswith("selinux_fc"):
350 fc_basename = os.path.basename(d[key])
351 fc_config = os.path.join(input_file, "META", fc_basename)
352 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700353
Daniel Norman72c626f2019-05-13 15:58:14 -0700354 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700355
Tom Cherryd14b8952018-08-09 14:26:00 -0700356 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700357 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700358 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700359 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700360
Tao Baof54216f2016-03-29 15:12:37 -0700361 # Redirect {system,vendor}_base_fs_file.
362 if "system_base_fs_file" in d:
363 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700364 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700365 if os.path.exists(system_base_fs_file):
366 d["system_base_fs_file"] = system_base_fs_file
367 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700368 logger.warning(
369 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700370 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700371
372 if "vendor_base_fs_file" in d:
373 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700374 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700375 if os.path.exists(vendor_base_fs_file):
376 d["vendor_base_fs_file"] = vendor_base_fs_file
377 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700378 logger.warning(
379 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700380 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700381
Doug Zongker37974732010-09-16 17:44:38 -0700382 def makeint(key):
383 if key in d:
384 d[key] = int(d[key], 0)
385
386 makeint("recovery_api_version")
387 makeint("blocksize")
388 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700389 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700390 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700391 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700392 makeint("recovery_size")
393 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800394 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700395
Tao Baoa57ab9f2018-08-24 12:08:38 -0700396 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
397 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
398 # cases, since it may load the info_dict from an old build (e.g. when
399 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800400 system_root_image = d.get("system_root_image") == "true"
401 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700402 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700403 if isinstance(input_file, zipfile.ZipFile):
404 if recovery_fstab_path not in input_file.namelist():
405 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
406 else:
407 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
408 if not os.path.exists(path):
409 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800410 d["fstab"] = LoadRecoveryFSTab(
411 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700412
Tao Bao76def242017-11-21 09:25:31 -0800413 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700414 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700415 if isinstance(input_file, zipfile.ZipFile):
416 if recovery_fstab_path not in input_file.namelist():
417 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
418 else:
419 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
420 if not os.path.exists(path):
421 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800422 d["fstab"] = LoadRecoveryFSTab(
423 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700424
Tianjie Xucfa86222016-03-07 16:31:19 -0800425 else:
426 d["fstab"] = None
427
Tianjie Xu861f4132018-09-12 11:49:33 -0700428 # Tries to load the build props for all partitions with care_map, including
429 # system and vendor.
430 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800431 partition_prop = "{}.build.prop".format(partition)
432 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700433 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800434 # Some partition might use /<partition>/etc/build.prop as the new path.
435 # TODO: try new path first when majority of them switch to the new path.
436 if not d[partition_prop]:
437 d[partition_prop] = LoadBuildProp(
438 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700439 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800440
441 # Set up the salt (based on fingerprint or thumbprint) that will be used when
442 # adding AVB footer.
443 if d.get("avb_enable") == "true":
444 fp = None
445 if "build.prop" in d:
446 build_prop = d["build.prop"]
447 if "ro.build.fingerprint" in build_prop:
448 fp = build_prop["ro.build.fingerprint"]
449 elif "ro.build.thumbprint" in build_prop:
450 fp = build_prop["ro.build.thumbprint"]
451 if fp:
452 d["avb_salt"] = sha256(fp).hexdigest()
453
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700454 return d
455
Tao Baod1de6f32017-03-01 16:38:48 -0800456
Tao Baobcd1d162017-08-26 13:10:26 -0700457def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700458 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700459 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700460 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700461 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700462 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700463 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700464
Tao Baod1de6f32017-03-01 16:38:48 -0800465
Daniel Norman4cc9df62019-07-18 10:11:07 -0700466def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900467 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700468 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900469
Daniel Norman4cc9df62019-07-18 10:11:07 -0700470
471def LoadDictionaryFromFile(file_path):
472 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900473 return LoadDictionaryFromLines(lines)
474
475
Michael Runge6e836112014-04-15 17:40:21 -0700476def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700477 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700478 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700479 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700480 if not line or line.startswith("#"):
481 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700482 if "=" in line:
483 name, value = line.split("=", 1)
484 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700485 return d
486
Tao Baod1de6f32017-03-01 16:38:48 -0800487
Tianjie Xucfa86222016-03-07 16:31:19 -0800488def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
489 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700490 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800491 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700492 self.mount_point = mount_point
493 self.fs_type = fs_type
494 self.device = device
495 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700496 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700497
498 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800499 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700500 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700501 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700502 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700503
Tao Baod1de6f32017-03-01 16:38:48 -0800504 assert fstab_version == 2
505
506 d = {}
507 for line in data.split("\n"):
508 line = line.strip()
509 if not line or line.startswith("#"):
510 continue
511
512 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
513 pieces = line.split()
514 if len(pieces) != 5:
515 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
516
517 # Ignore entries that are managed by vold.
518 options = pieces[4]
519 if "voldmanaged=" in options:
520 continue
521
522 # It's a good line, parse it.
523 length = 0
524 options = options.split(",")
525 for i in options:
526 if i.startswith("length="):
527 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800528 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800529 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700530 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800531
Tao Baod1de6f32017-03-01 16:38:48 -0800532 mount_flags = pieces[3]
533 # Honor the SELinux context if present.
534 context = None
535 for i in mount_flags.split(","):
536 if i.startswith("context="):
537 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800538
Tao Baod1de6f32017-03-01 16:38:48 -0800539 mount_point = pieces[1]
540 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
541 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800542
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700543 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700544 # system. Other areas assume system is always at "/system" so point /system
545 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700546 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800547 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700548 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700549 return d
550
551
Doug Zongker37974732010-09-16 17:44:38 -0700552def DumpInfoDict(d):
553 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700554 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700555
Dan Albert8b72aef2015-03-23 19:13:21 -0700556
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700557def MergeDynamicPartitionInfoDicts(framework_dict,
558 vendor_dict,
559 include_dynamic_partition_list=True,
560 size_prefix="",
561 size_suffix="",
562 list_prefix="",
563 list_suffix=""):
564 """Merges dynamic partition info variables.
565
566 Args:
567 framework_dict: The dictionary of dynamic partition info variables from the
568 partial framework target files.
569 vendor_dict: The dictionary of dynamic partition info variables from the
570 partial vendor target files.
571 include_dynamic_partition_list: If true, merges the dynamic_partition_list
572 variable. Not all use cases need this variable merged.
573 size_prefix: The prefix in partition group size variables that precedes the
574 name of the partition group. For example, partition group 'group_a' with
575 corresponding size variable 'super_group_a_group_size' would have the
576 size_prefix 'super_'.
577 size_suffix: Similar to size_prefix but for the variable's suffix. For
578 example, 'super_group_a_group_size' would have size_suffix '_group_size'.
579 list_prefix: Similar to size_prefix but for the partition group's
580 partition_list variable.
581 list_suffix: Similar to size_suffix but for the partition group's
582 partition_list variable.
583
584 Returns:
585 The merged dynamic partition info dictionary.
586 """
587 merged_dict = {}
588 # Partition groups and group sizes are defined by the vendor dict because
589 # these values may vary for each board that uses a shared system image.
590 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
591 if include_dynamic_partition_list:
592 framework_dynamic_partition_list = framework_dict.get(
593 "dynamic_partition_list", "")
594 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list",
595 "")
596 merged_dict["dynamic_partition_list"] = (
597 "%s %s" % (framework_dynamic_partition_list,
598 vendor_dynamic_partition_list)).strip()
599 for partition_group in merged_dict["super_partition_groups"].split(" "):
600 # Set the partition group's size using the value from the vendor dict.
601 key = "%s%s%s" % (size_prefix, partition_group, size_suffix)
602 if key not in vendor_dict:
603 raise ValueError("Vendor dict does not contain required key %s." % key)
604 merged_dict[key] = vendor_dict[key]
605
606 # Set the partition group's partition list using a concatenation of the
607 # framework and vendor partition lists.
608 key = "%s%s%s" % (list_prefix, partition_group, list_suffix)
609 merged_dict[key] = (
610 "%s %s" %
611 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
612 return merged_dict
613
614
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800615def AppendAVBSigningArgs(cmd, partition):
616 """Append signing arguments for avbtool."""
617 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
618 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700619 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
620 new_key_path = os.path.join(OPTIONS.search_path, key_path)
621 if os.path.exists(new_key_path):
622 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800623 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
624 if key_path and algorithm:
625 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700626 avb_salt = OPTIONS.info_dict.get("avb_salt")
627 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700628 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700629 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800630
631
Daniel Norman276f0622019-07-26 14:13:51 -0700632def GetAvbPartitionArg(partition, image, info_dict = None):
633 """Returns the VBMeta arguments for partition.
634
635 It sets up the VBMeta argument by including the partition descriptor from the
636 given 'image', or by configuring the partition as a chained partition.
637
638 Args:
639 partition: The name of the partition (e.g. "system").
640 image: The path to the partition image.
641 info_dict: A dict returned by common.LoadInfoDict(). Will use
642 OPTIONS.info_dict if None has been given.
643
644 Returns:
645 A list of VBMeta arguments.
646 """
647 if info_dict is None:
648 info_dict = OPTIONS.info_dict
649
650 # Check if chain partition is used.
651 key_path = info_dict.get("avb_" + partition + "_key_path")
652 if key_path:
653 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
654 return ["--chain_partition", chained_partition_arg]
655 else:
656 return ["--include_descriptors_from_image", image]
657
658
Tao Bao02a08592018-07-22 12:40:45 -0700659def GetAvbChainedPartitionArg(partition, info_dict, key=None):
660 """Constructs and returns the arg to build or verify a chained partition.
661
662 Args:
663 partition: The partition name.
664 info_dict: The info dict to look up the key info and rollback index
665 location.
666 key: The key to be used for building or verifying the partition. Defaults to
667 the key listed in info_dict.
668
669 Returns:
670 A string of form "partition:rollback_index_location:key" that can be used to
671 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700672 """
673 if key is None:
674 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700675 if key and not os.path.exists(key) and OPTIONS.search_path:
676 new_key_path = os.path.join(OPTIONS.search_path, key)
677 if os.path.exists(new_key_path):
678 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700679 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700680 rollback_index_location = info_dict[
681 "avb_" + partition + "_rollback_index_location"]
682 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
683
684
Daniel Norman276f0622019-07-26 14:13:51 -0700685def BuildVBMeta(image_path, partitions, name, needed_partitions):
686 """Creates a VBMeta image.
687
688 It generates the requested VBMeta image. The requested image could be for
689 top-level or chained VBMeta image, which is determined based on the name.
690
691 Args:
692 image_path: The output path for the new VBMeta image.
693 partitions: A dict that's keyed by partition names with image paths as
694 values. Only valid partition names are accepted, as listed in
695 common.AVB_PARTITIONS.
696 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
697 needed_partitions: Partitions whose descriptors should be included into the
698 generated VBMeta image.
699
700 Raises:
701 AssertionError: On invalid input args.
702 """
703 avbtool = OPTIONS.info_dict["avb_avbtool"]
704 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
705 AppendAVBSigningArgs(cmd, name)
706
707 for partition, path in partitions.items():
708 if partition not in needed_partitions:
709 continue
710 assert (partition in AVB_PARTITIONS or
711 partition in AVB_VBMETA_PARTITIONS), \
712 'Unknown partition: {}'.format(partition)
713 assert os.path.exists(path), \
714 'Failed to find {} for {}'.format(path, partition)
715 cmd.extend(GetAvbPartitionArg(partition, path))
716
717 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
718 if args and args.strip():
719 split_args = shlex.split(args)
720 for index, arg in enumerate(split_args[:-1]):
721 # Sanity check that the image file exists. Some images might be defined
722 # as a path relative to source tree, which may not be available at the
723 # same location when running this script (we have the input target_files
724 # zip only). For such cases, we additionally scan other locations (e.g.
725 # IMAGES/, RADIO/, etc) before bailing out.
726 if arg == '--include_descriptors_from_image':
727 image_path = split_args[index + 1]
728 if os.path.exists(image_path):
729 continue
730 found = False
731 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
732 alt_path = os.path.join(
733 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
734 if os.path.exists(alt_path):
735 split_args[index + 1] = alt_path
736 found = True
737 break
738 assert found, 'Failed to find {}'.format(image_path)
739 cmd.extend(split_args)
740
741 RunAndCheckOutput(cmd)
742
743
Steve Mucklee1b10862019-07-10 10:49:37 -0700744def _MakeRamdisk(sourcedir, fs_config_file=None):
745 ramdisk_img = tempfile.NamedTemporaryFile()
746
747 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
748 cmd = ["mkbootfs", "-f", fs_config_file,
749 os.path.join(sourcedir, "RAMDISK")]
750 else:
751 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
752 p1 = Run(cmd, stdout=subprocess.PIPE)
753 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
754
755 p2.wait()
756 p1.wait()
757 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
758 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
759
760 return ramdisk_img
761
762
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700763def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800764 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700765 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700766
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700767 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800768 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
769 we are building a two-step special image (i.e. building a recovery image to
770 be loaded into /boot in two-step OTAs).
771
772 Return the image data, or None if sourcedir does not appear to contains files
773 for building the requested image.
774 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700775
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700776 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
777 return None
778
779 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700780 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700781
Doug Zongkerd5131602012-08-02 14:46:42 -0700782 if info_dict is None:
783 info_dict = OPTIONS.info_dict
784
Doug Zongkereef39442009-04-02 12:14:19 -0700785 img = tempfile.NamedTemporaryFile()
786
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700787 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -0700788 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -0700789
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800790 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
791 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
792
793 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700794
Benoit Fradina45a8682014-07-14 21:00:43 +0200795 fn = os.path.join(sourcedir, "second")
796 if os.access(fn, os.F_OK):
797 cmd.append("--second")
798 cmd.append(fn)
799
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800800 fn = os.path.join(sourcedir, "dtb")
801 if os.access(fn, os.F_OK):
802 cmd.append("--dtb")
803 cmd.append(fn)
804
Doug Zongker171f1cd2009-06-15 22:36:37 -0700805 fn = os.path.join(sourcedir, "cmdline")
806 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700807 cmd.append("--cmdline")
808 cmd.append(open(fn).read().rstrip("\n"))
809
810 fn = os.path.join(sourcedir, "base")
811 if os.access(fn, os.F_OK):
812 cmd.append("--base")
813 cmd.append(open(fn).read().rstrip("\n"))
814
Ying Wang4de6b5b2010-08-25 14:29:34 -0700815 fn = os.path.join(sourcedir, "pagesize")
816 if os.access(fn, os.F_OK):
817 cmd.append("--pagesize")
818 cmd.append(open(fn).read().rstrip("\n"))
819
Tao Bao76def242017-11-21 09:25:31 -0800820 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700821 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700822 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700823
Tao Bao76def242017-11-21 09:25:31 -0800824 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000825 if args and args.strip():
826 cmd.extend(shlex.split(args))
827
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700828 if has_ramdisk:
829 cmd.extend(["--ramdisk", ramdisk_img.name])
830
Tao Baod95e9fd2015-03-29 23:07:41 -0700831 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800832 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700833 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700834 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700835 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700836 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700837
Tao Baobf70c312017-07-11 17:27:55 -0700838 # "boot" or "recovery", without extension.
839 partition_name = os.path.basename(sourcedir).lower()
840
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800841 if partition_name == "recovery":
842 if info_dict.get("include_recovery_dtbo") == "true":
843 fn = os.path.join(sourcedir, "recovery_dtbo")
844 cmd.extend(["--recovery_dtbo", fn])
845 if info_dict.get("include_recovery_acpio") == "true":
846 fn = os.path.join(sourcedir, "recovery_acpio")
847 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700848
Tao Bao986ee862018-10-04 15:46:16 -0700849 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700850
Tao Bao76def242017-11-21 09:25:31 -0800851 if (info_dict.get("boot_signer") == "true" and
852 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800853 # Hard-code the path as "/boot" for two-step special recovery image (which
854 # will be loaded into /boot during the two-step OTA).
855 if two_step_image:
856 path = "/boot"
857 else:
Tao Baobf70c312017-07-11 17:27:55 -0700858 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700859 cmd = [OPTIONS.boot_signer_path]
860 cmd.extend(OPTIONS.boot_signer_args)
861 cmd.extend([path, img.name,
862 info_dict["verity_key"] + ".pk8",
863 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700864 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700865
Tao Baod95e9fd2015-03-29 23:07:41 -0700866 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800867 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700868 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700869 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800870 # We have switched from the prebuilt futility binary to using the tool
871 # (futility-host) built from the source. Override the setting in the old
872 # TF.zip.
873 futility = info_dict["futility"]
874 if futility.startswith("prebuilts/"):
875 futility = "futility-host"
876 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700877 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700878 info_dict["vboot_key"] + ".vbprivk",
879 info_dict["vboot_subkey"] + ".vbprivk",
880 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700881 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700882 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700883
Tao Baof3282b42015-04-01 11:21:55 -0700884 # Clean up the temp files.
885 img_unsigned.close()
886 img_keyblock.close()
887
David Zeuthen8fecb282017-12-01 16:24:01 -0500888 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800889 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700890 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500891 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400892 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700893 "--partition_size", str(part_size), "--partition_name",
894 partition_name]
895 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500896 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400897 if args and args.strip():
898 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700899 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500900
901 img.seek(os.SEEK_SET, 0)
902 data = img.read()
903
904 if has_ramdisk:
905 ramdisk_img.close()
906 img.close()
907
908 return data
909
910
Doug Zongkerd5131602012-08-02 14:46:42 -0700911def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800912 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700913 """Return a File object with the desired bootable image.
914
915 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
916 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
917 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700918
Doug Zongker55d93282011-01-25 17:03:34 -0800919 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
920 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700921 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800922 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700923
924 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
925 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700926 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700927 return File.FromLocalFile(name, prebuilt_path)
928
Tao Bao32fcdab2018-10-12 10:30:39 -0700929 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700930
931 if info_dict is None:
932 info_dict = OPTIONS.info_dict
933
934 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800935 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
936 # for recovery.
937 has_ramdisk = (info_dict.get("system_root_image") != "true" or
938 prebuilt_name != "boot.img" or
939 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700940
Doug Zongker6f1d0312014-08-22 08:07:12 -0700941 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400942 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
943 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800944 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700945 if data:
946 return File(name, data)
947 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800948
Doug Zongkereef39442009-04-02 12:14:19 -0700949
Steve Mucklee1b10862019-07-10 10:49:37 -0700950def _BuildVendorBootImage(sourcedir, info_dict=None):
951 """Build a vendor boot image from the specified sourcedir.
952
953 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
954 turn them into a vendor boot image.
955
956 Return the image data, or None if sourcedir does not appear to contains files
957 for building the requested image.
958 """
959
960 if info_dict is None:
961 info_dict = OPTIONS.info_dict
962
963 img = tempfile.NamedTemporaryFile()
964
965 ramdisk_img = _MakeRamdisk(sourcedir)
966
967 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
968 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
969
970 cmd = [mkbootimg]
971
972 fn = os.path.join(sourcedir, "dtb")
973 if os.access(fn, os.F_OK):
974 cmd.append("--dtb")
975 cmd.append(fn)
976
977 fn = os.path.join(sourcedir, "vendor_cmdline")
978 if os.access(fn, os.F_OK):
979 cmd.append("--vendor_cmdline")
980 cmd.append(open(fn).read().rstrip("\n"))
981
982 fn = os.path.join(sourcedir, "base")
983 if os.access(fn, os.F_OK):
984 cmd.append("--base")
985 cmd.append(open(fn).read().rstrip("\n"))
986
987 fn = os.path.join(sourcedir, "pagesize")
988 if os.access(fn, os.F_OK):
989 cmd.append("--pagesize")
990 cmd.append(open(fn).read().rstrip("\n"))
991
992 args = info_dict.get("mkbootimg_args")
993 if args and args.strip():
994 cmd.extend(shlex.split(args))
995
996 args = info_dict.get("mkbootimg_version_args")
997 if args and args.strip():
998 cmd.extend(shlex.split(args))
999
1000 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1001 cmd.extend(["--vendor_boot", img.name])
1002
1003 RunAndCheckOutput(cmd)
1004
1005 # AVB: if enabled, calculate and add hash.
1006 if info_dict.get("avb_enable") == "true":
1007 avbtool = info_dict["avb_avbtool"]
1008 part_size = info_dict["vendor_boot_size"]
1009 cmd = [avbtool, "add_hash_footer", "--image", img.name,
1010 "--partition_size", str(part_size), "--partition_name vendor_boot"]
1011 AppendAVBSigningArgs(cmd, "vendor_boot")
1012 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1013 if args and args.strip():
1014 cmd.extend(shlex.split(args))
1015 RunAndCheckOutput(cmd)
1016
1017 img.seek(os.SEEK_SET, 0)
1018 data = img.read()
1019
1020 ramdisk_img.close()
1021 img.close()
1022
1023 return data
1024
1025
1026def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1027 info_dict=None):
1028 """Return a File object with the desired vendor boot image.
1029
1030 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1031 the source files in 'unpack_dir'/'tree_subdir'."""
1032
1033 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1034 if os.path.exists(prebuilt_path):
1035 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1036 return File.FromLocalFile(name, prebuilt_path)
1037
1038 logger.info("building image from target_files %s...", tree_subdir)
1039
1040 if info_dict is None:
1041 info_dict = OPTIONS.info_dict
1042
1043 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1044 if data:
1045 return File(name, data)
1046 return None
1047
1048
Narayan Kamatha07bf042017-08-14 14:49:21 +01001049def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001050 """Gunzips the given gzip compressed file to a given output file."""
1051 with gzip.open(in_filename, "rb") as in_file, \
1052 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001053 shutil.copyfileobj(in_file, out_file)
1054
1055
Tao Bao0ff15de2019-03-20 11:26:06 -07001056def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001057 """Unzips the archive to the given directory.
1058
1059 Args:
1060 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001061 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001062 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1063 archvie. Non-matching patterns will be filtered out. If there's no match
1064 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001065 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001066 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001067 if patterns is not None:
1068 # Filter out non-matching patterns. unzip will complain otherwise.
1069 with zipfile.ZipFile(filename) as input_zip:
1070 names = input_zip.namelist()
1071 filtered = [
1072 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1073
1074 # There isn't any matching files. Don't unzip anything.
1075 if not filtered:
1076 return
1077 cmd.extend(filtered)
1078
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001079 RunAndCheckOutput(cmd)
1080
1081
Doug Zongker75f17362009-12-08 13:46:44 -08001082def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001083 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001084
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001085 Args:
1086 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1087 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1088
1089 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1090 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001091
Tao Bao1c830bf2017-12-25 10:43:47 -08001092 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001093 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001094 """
Doug Zongkereef39442009-04-02 12:14:19 -07001095
Tao Bao1c830bf2017-12-25 10:43:47 -08001096 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001097 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1098 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001099 UnzipToDir(m.group(1), tmp, pattern)
1100 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001101 filename = m.group(1)
1102 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001103 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001104
Tao Baodba59ee2018-01-09 13:21:02 -08001105 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001106
1107
Yifan Hong8a66a712019-04-04 15:37:57 -07001108def GetUserImage(which, tmpdir, input_zip,
1109 info_dict=None,
1110 allow_shared_blocks=None,
1111 hashtree_info_generator=None,
1112 reset_file_map=False):
1113 """Returns an Image object suitable for passing to BlockImageDiff.
1114
1115 This function loads the specified image from the given path. If the specified
1116 image is sparse, it also performs additional processing for OTA purpose. For
1117 example, it always adds block 0 to clobbered blocks list. It also detects
1118 files that cannot be reconstructed from the block list, for whom we should
1119 avoid applying imgdiff.
1120
1121 Args:
1122 which: The partition name.
1123 tmpdir: The directory that contains the prebuilt image and block map file.
1124 input_zip: The target-files ZIP archive.
1125 info_dict: The dict to be looked up for relevant info.
1126 allow_shared_blocks: If image is sparse, whether having shared blocks is
1127 allowed. If none, it is looked up from info_dict.
1128 hashtree_info_generator: If present and image is sparse, generates the
1129 hashtree_info for this sparse image.
1130 reset_file_map: If true and image is sparse, reset file map before returning
1131 the image.
1132 Returns:
1133 A Image object. If it is a sparse image and reset_file_map is False, the
1134 image will have file_map info loaded.
1135 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001136 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001137 info_dict = LoadInfoDict(input_zip)
1138
1139 is_sparse = info_dict.get("extfs_sparse_flag")
1140
1141 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1142 # shared blocks (i.e. some blocks will show up in multiple files' block
1143 # list). We can only allocate such shared blocks to the first "owner", and
1144 # disable imgdiff for all later occurrences.
1145 if allow_shared_blocks is None:
1146 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1147
1148 if is_sparse:
1149 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1150 hashtree_info_generator)
1151 if reset_file_map:
1152 img.ResetFileMap()
1153 return img
1154 else:
1155 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1156
1157
1158def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1159 """Returns a Image object suitable for passing to BlockImageDiff.
1160
1161 This function loads the specified non-sparse image from the given path.
1162
1163 Args:
1164 which: The partition name.
1165 tmpdir: The directory that contains the prebuilt image and block map file.
1166 Returns:
1167 A Image object.
1168 """
1169 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1170 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1171
1172 # The image and map files must have been created prior to calling
1173 # ota_from_target_files.py (since LMP).
1174 assert os.path.exists(path) and os.path.exists(mappath)
1175
Tianjie Xu41976c72019-07-03 13:57:01 -07001176 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1177
Yifan Hong8a66a712019-04-04 15:37:57 -07001178
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001179def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1180 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001181 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1182
1183 This function loads the specified sparse image from the given path, and
1184 performs additional processing for OTA purpose. For example, it always adds
1185 block 0 to clobbered blocks list. It also detects files that cannot be
1186 reconstructed from the block list, for whom we should avoid applying imgdiff.
1187
1188 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001189 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001190 tmpdir: The directory that contains the prebuilt image and block map file.
1191 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001192 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001193 hashtree_info_generator: If present, generates the hashtree_info for this
1194 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001195 Returns:
1196 A SparseImage object, with file_map info loaded.
1197 """
Tao Baoc765cca2018-01-31 17:32:40 -08001198 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1199 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1200
1201 # The image and map files must have been created prior to calling
1202 # ota_from_target_files.py (since LMP).
1203 assert os.path.exists(path) and os.path.exists(mappath)
1204
1205 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1206 # it to clobbered_blocks so that it will be written to the target
1207 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1208 clobbered_blocks = "0"
1209
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001210 image = sparse_img.SparseImage(
1211 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1212 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001213
1214 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1215 # if they contain all zeros. We can't reconstruct such a file from its block
1216 # list. Tag such entries accordingly. (Bug: 65213616)
1217 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001218 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001219 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001220 continue
1221
Tom Cherryd14b8952018-08-09 14:26:00 -07001222 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1223 # filename listed in system.map may contain an additional leading slash
1224 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1225 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001226 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001227
Tom Cherryd14b8952018-08-09 14:26:00 -07001228 # Special handling another case, where files not under /system
1229 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001230 if which == 'system' and not arcname.startswith('SYSTEM'):
1231 arcname = 'ROOT/' + arcname
1232
1233 assert arcname in input_zip.namelist(), \
1234 "Failed to find the ZIP entry for {}".format(entry)
1235
Tao Baoc765cca2018-01-31 17:32:40 -08001236 info = input_zip.getinfo(arcname)
1237 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001238
1239 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001240 # image, check the original block list to determine its completeness. Note
1241 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001242 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001243 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001244
Tao Baoc765cca2018-01-31 17:32:40 -08001245 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1246 ranges.extra['incomplete'] = True
1247
1248 return image
1249
1250
Doug Zongkereef39442009-04-02 12:14:19 -07001251def GetKeyPasswords(keylist):
1252 """Given a list of keys, prompt the user to enter passwords for
1253 those which require them. Return a {key: password} dict. password
1254 will be None if the key has no password."""
1255
Doug Zongker8ce7c252009-05-22 13:34:54 -07001256 no_passwords = []
1257 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001258 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001259 devnull = open("/dev/null", "w+b")
1260 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001261 # We don't need a password for things that aren't really keys.
1262 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001263 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001264 continue
1265
T.R. Fullhart37e10522013-03-18 10:31:26 -07001266 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001267 "-inform", "DER", "-nocrypt"],
1268 stdin=devnull.fileno(),
1269 stdout=devnull.fileno(),
1270 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001271 p.communicate()
1272 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001273 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001274 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001275 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001276 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1277 "-inform", "DER", "-passin", "pass:"],
1278 stdin=devnull.fileno(),
1279 stdout=devnull.fileno(),
1280 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001281 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001282 if p.returncode == 0:
1283 # Encrypted key with empty string as password.
1284 key_passwords[k] = ''
1285 elif stderr.startswith('Error decrypting key'):
1286 # Definitely encrypted key.
1287 # It would have said "Error reading key" if it didn't parse correctly.
1288 need_passwords.append(k)
1289 else:
1290 # Potentially, a type of key that openssl doesn't understand.
1291 # We'll let the routines in signapk.jar handle it.
1292 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001293 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001294
T.R. Fullhart37e10522013-03-18 10:31:26 -07001295 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001296 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001297 return key_passwords
1298
1299
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001300def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001301 """Gets the minSdkVersion declared in the APK.
1302
changho.shin0f125362019-07-08 10:59:00 +09001303 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001304 This can be both a decimal number (API Level) or a codename.
1305
1306 Args:
1307 apk_name: The APK filename.
1308
1309 Returns:
1310 The parsed SDK version string.
1311
1312 Raises:
1313 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001314 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001315 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001316 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001317 stderr=subprocess.PIPE)
1318 stdoutdata, stderrdata = proc.communicate()
1319 if proc.returncode != 0:
1320 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001321 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001322 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001323
Tao Baof47bf0f2018-03-21 23:28:51 -07001324 for line in stdoutdata.split("\n"):
1325 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001326 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1327 if m:
1328 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001329 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001330
1331
1332def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001333 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001334
Tao Baof47bf0f2018-03-21 23:28:51 -07001335 If minSdkVersion is set to a codename, it is translated to a number using the
1336 provided map.
1337
1338 Args:
1339 apk_name: The APK filename.
1340
1341 Returns:
1342 The parsed SDK version number.
1343
1344 Raises:
1345 ExternalError: On failing to get the min SDK version number.
1346 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001347 version = GetMinSdkVersion(apk_name)
1348 try:
1349 return int(version)
1350 except ValueError:
1351 # Not a decimal number. Codename?
1352 if version in codename_to_api_level_map:
1353 return codename_to_api_level_map[version]
1354 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001355 raise ExternalError(
1356 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1357 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001358
1359
1360def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001361 codename_to_api_level_map=None, whole_file=False,
1362 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001363 """Sign the input_name zip/jar/apk, producing output_name. Use the
1364 given key and password (the latter may be None if the key does not
1365 have a password.
1366
Doug Zongker951495f2009-08-14 12:44:19 -07001367 If whole_file is true, use the "-w" option to SignApk to embed a
1368 signature that covers the whole file in the archive comment of the
1369 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001370
1371 min_api_level is the API Level (int) of the oldest platform this file may end
1372 up on. If not specified for an APK, the API Level is obtained by interpreting
1373 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1374
1375 codename_to_api_level_map is needed to translate the codename which may be
1376 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001377
1378 Caller may optionally specify extra args to be passed to SignApk, which
1379 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001380 """
Tao Bao76def242017-11-21 09:25:31 -08001381 if codename_to_api_level_map is None:
1382 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001383 if extra_signapk_args is None:
1384 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001385
Alex Klyubin9667b182015-12-10 13:38:50 -08001386 java_library_path = os.path.join(
1387 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1388
Tao Baoe95540e2016-11-08 12:08:53 -08001389 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1390 ["-Djava.library.path=" + java_library_path,
1391 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001392 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001393 if whole_file:
1394 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001395
1396 min_sdk_version = min_api_level
1397 if min_sdk_version is None:
1398 if not whole_file:
1399 min_sdk_version = GetMinSdkVersionInt(
1400 input_name, codename_to_api_level_map)
1401 if min_sdk_version is not None:
1402 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1403
T.R. Fullhart37e10522013-03-18 10:31:26 -07001404 cmd.extend([key + OPTIONS.public_key_suffix,
1405 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001406 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001407
Tao Bao73dd4f42018-10-04 16:25:33 -07001408 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001409 if password is not None:
1410 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001411 stdoutdata, _ = proc.communicate(password)
1412 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001413 raise ExternalError(
1414 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001415 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001416
Doug Zongkereef39442009-04-02 12:14:19 -07001417
Doug Zongker37974732010-09-16 17:44:38 -07001418def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001419 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001420
Tao Bao9dd909e2017-11-14 11:27:32 -08001421 For non-AVB images, raise exception if the data is too big. Print a warning
1422 if the data is nearing the maximum size.
1423
1424 For AVB images, the actual image size should be identical to the limit.
1425
1426 Args:
1427 data: A string that contains all the data for the partition.
1428 target: The partition name. The ".img" suffix is optional.
1429 info_dict: The dict to be looked up for relevant info.
1430 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001431 if target.endswith(".img"):
1432 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001433 mount_point = "/" + target
1434
Ying Wangf8824af2014-06-03 14:07:27 -07001435 fs_type = None
1436 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001437 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001438 if mount_point == "/userdata":
1439 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001440 p = info_dict["fstab"][mount_point]
1441 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001442 device = p.device
1443 if "/" in device:
1444 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001445 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001446 if not fs_type or not limit:
1447 return
Doug Zongkereef39442009-04-02 12:14:19 -07001448
Andrew Boie0f9aec82012-02-14 09:32:52 -08001449 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001450 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1451 # path.
1452 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1453 if size != limit:
1454 raise ExternalError(
1455 "Mismatching image size for %s: expected %d actual %d" % (
1456 target, limit, size))
1457 else:
1458 pct = float(size) * 100.0 / limit
1459 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1460 if pct >= 99.0:
1461 raise ExternalError(msg)
1462 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001463 logger.warning("\n WARNING: %s\n", msg)
1464 else:
1465 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001466
1467
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001468def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001469 """Parses the APK certs info from a given target-files zip.
1470
1471 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1472 tuple with the following elements: (1) a dictionary that maps packages to
1473 certs (based on the "certificate" and "private_key" attributes in the file;
1474 (2) a string representing the extension of compressed APKs in the target files
1475 (e.g ".gz", ".bro").
1476
1477 Args:
1478 tf_zip: The input target_files ZipFile (already open).
1479
1480 Returns:
1481 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1482 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1483 no compressed APKs.
1484 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001485 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001486 compressed_extension = None
1487
Tao Bao0f990332017-09-08 19:02:54 -07001488 # META/apkcerts.txt contains the info for _all_ the packages known at build
1489 # time. Filter out the ones that are not installed.
1490 installed_files = set()
1491 for name in tf_zip.namelist():
1492 basename = os.path.basename(name)
1493 if basename:
1494 installed_files.add(basename)
1495
Tao Baoda30cfa2017-12-01 16:19:46 -08001496 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001497 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001498 if not line:
1499 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001500 m = re.match(
1501 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1502 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1503 line)
1504 if not m:
1505 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001506
Tao Bao818ddf52018-01-05 11:17:34 -08001507 matches = m.groupdict()
1508 cert = matches["CERT"]
1509 privkey = matches["PRIVKEY"]
1510 name = matches["NAME"]
1511 this_compressed_extension = matches["COMPRESSED"]
1512
1513 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1514 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1515 if cert in SPECIAL_CERT_STRINGS and not privkey:
1516 certmap[name] = cert
1517 elif (cert.endswith(OPTIONS.public_key_suffix) and
1518 privkey.endswith(OPTIONS.private_key_suffix) and
1519 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1520 certmap[name] = cert[:-public_key_suffix_len]
1521 else:
1522 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1523
1524 if not this_compressed_extension:
1525 continue
1526
1527 # Only count the installed files.
1528 filename = name + '.' + this_compressed_extension
1529 if filename not in installed_files:
1530 continue
1531
1532 # Make sure that all the values in the compression map have the same
1533 # extension. We don't support multiple compression methods in the same
1534 # system image.
1535 if compressed_extension:
1536 if this_compressed_extension != compressed_extension:
1537 raise ValueError(
1538 "Multiple compressed extensions: {} vs {}".format(
1539 compressed_extension, this_compressed_extension))
1540 else:
1541 compressed_extension = this_compressed_extension
1542
1543 return (certmap,
1544 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001545
1546
Doug Zongkereef39442009-04-02 12:14:19 -07001547COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001548Global options
1549
1550 -p (--path) <dir>
1551 Prepend <dir>/bin to the list of places to search for binaries run by this
1552 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001553
Doug Zongker05d3dea2009-06-22 11:32:31 -07001554 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001555 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001556
Tao Bao30df8b42018-04-23 15:32:53 -07001557 -x (--extra) <key=value>
1558 Add a key/value pair to the 'extras' dict, which device-specific extension
1559 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001560
Doug Zongkereef39442009-04-02 12:14:19 -07001561 -v (--verbose)
1562 Show command lines being executed.
1563
1564 -h (--help)
1565 Display this usage message and exit.
1566"""
1567
1568def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001569 print(docstring.rstrip("\n"))
1570 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001571
1572
1573def ParseOptions(argv,
1574 docstring,
1575 extra_opts="", extra_long_opts=(),
1576 extra_option_handler=None):
1577 """Parse the options in argv and return any arguments that aren't
1578 flags. docstring is the calling module's docstring, to be displayed
1579 for errors and -h. extra_opts and extra_long_opts are for flags
1580 defined by the caller, which are processed by passing them to
1581 extra_option_handler."""
1582
1583 try:
1584 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001585 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001586 ["help", "verbose", "path=", "signapk_path=",
1587 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001588 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001589 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1590 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001591 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001592 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001593 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001594 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001595 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001596 sys.exit(2)
1597
Doug Zongkereef39442009-04-02 12:14:19 -07001598 for o, a in opts:
1599 if o in ("-h", "--help"):
1600 Usage(docstring)
1601 sys.exit()
1602 elif o in ("-v", "--verbose"):
1603 OPTIONS.verbose = True
1604 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001605 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001606 elif o in ("--signapk_path",):
1607 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001608 elif o in ("--signapk_shared_library_path",):
1609 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001610 elif o in ("--extra_signapk_args",):
1611 OPTIONS.extra_signapk_args = shlex.split(a)
1612 elif o in ("--java_path",):
1613 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001614 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001615 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001616 elif o in ("--public_key_suffix",):
1617 OPTIONS.public_key_suffix = a
1618 elif o in ("--private_key_suffix",):
1619 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001620 elif o in ("--boot_signer_path",):
1621 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001622 elif o in ("--boot_signer_args",):
1623 OPTIONS.boot_signer_args = shlex.split(a)
1624 elif o in ("--verity_signer_path",):
1625 OPTIONS.verity_signer_path = a
1626 elif o in ("--verity_signer_args",):
1627 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001628 elif o in ("-s", "--device_specific"):
1629 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001630 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001631 key, value = a.split("=", 1)
1632 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001633 else:
1634 if extra_option_handler is None or not extra_option_handler(o, a):
1635 assert False, "unknown option \"%s\"" % (o,)
1636
Doug Zongker85448772014-09-09 14:59:20 -07001637 if OPTIONS.search_path:
1638 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1639 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001640
1641 return args
1642
1643
Tao Bao4c851b12016-09-19 13:54:38 -07001644def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001645 """Make a temp file and add it to the list of things to be deleted
1646 when Cleanup() is called. Return the filename."""
1647 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1648 os.close(fd)
1649 OPTIONS.tempfiles.append(fn)
1650 return fn
1651
1652
Tao Bao1c830bf2017-12-25 10:43:47 -08001653def MakeTempDir(prefix='tmp', suffix=''):
1654 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1655
1656 Returns:
1657 The absolute pathname of the new directory.
1658 """
1659 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1660 OPTIONS.tempfiles.append(dir_name)
1661 return dir_name
1662
1663
Doug Zongkereef39442009-04-02 12:14:19 -07001664def Cleanup():
1665 for i in OPTIONS.tempfiles:
1666 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001667 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001668 else:
1669 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001670 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001671
1672
1673class PasswordManager(object):
1674 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001675 self.editor = os.getenv("EDITOR")
1676 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001677
1678 def GetPasswords(self, items):
1679 """Get passwords corresponding to each string in 'items',
1680 returning a dict. (The dict may have keys in addition to the
1681 values in 'items'.)
1682
1683 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1684 user edit that file to add more needed passwords. If no editor is
1685 available, or $ANDROID_PW_FILE isn't define, prompts the user
1686 interactively in the ordinary way.
1687 """
1688
1689 current = self.ReadFile()
1690
1691 first = True
1692 while True:
1693 missing = []
1694 for i in items:
1695 if i not in current or not current[i]:
1696 missing.append(i)
1697 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001698 if not missing:
1699 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001700
1701 for i in missing:
1702 current[i] = ""
1703
1704 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001705 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001706 if sys.version_info[0] >= 3:
1707 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001708 answer = raw_input("try to edit again? [y]> ").strip()
1709 if answer and answer[0] not in 'yY':
1710 raise RuntimeError("key passwords unavailable")
1711 first = False
1712
1713 current = self.UpdateAndReadFile(current)
1714
Dan Albert8b72aef2015-03-23 19:13:21 -07001715 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001716 """Prompt the user to enter a value (password) for each key in
1717 'current' whose value is fales. Returns a new dict with all the
1718 values.
1719 """
1720 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001721 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001722 if v:
1723 result[k] = v
1724 else:
1725 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001726 result[k] = getpass.getpass(
1727 "Enter password for %s key> " % k).strip()
1728 if result[k]:
1729 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001730 return result
1731
1732 def UpdateAndReadFile(self, current):
1733 if not self.editor or not self.pwfile:
1734 return self.PromptResult(current)
1735
1736 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001737 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001738 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1739 f.write("# (Additional spaces are harmless.)\n\n")
1740
1741 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001742 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001743 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001744 f.write("[[[ %s ]]] %s\n" % (v, k))
1745 if not v and first_line is None:
1746 # position cursor on first line with no password.
1747 first_line = i + 4
1748 f.close()
1749
Tao Bao986ee862018-10-04 15:46:16 -07001750 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001751
1752 return self.ReadFile()
1753
1754 def ReadFile(self):
1755 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001756 if self.pwfile is None:
1757 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001758 try:
1759 f = open(self.pwfile, "r")
1760 for line in f:
1761 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001762 if not line or line[0] == '#':
1763 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001764 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1765 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001766 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001767 else:
1768 result[m.group(2)] = m.group(1)
1769 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001770 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001771 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001772 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001773 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001774
1775
Dan Albert8e0178d2015-01-27 15:53:15 -08001776def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1777 compress_type=None):
1778 import datetime
1779
1780 # http://b/18015246
1781 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1782 # for files larger than 2GiB. We can work around this by adjusting their
1783 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1784 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1785 # it isn't clear to me exactly what circumstances cause this).
1786 # `zipfile.write()` must be used directly to work around this.
1787 #
1788 # This mess can be avoided if we port to python3.
1789 saved_zip64_limit = zipfile.ZIP64_LIMIT
1790 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1791
1792 if compress_type is None:
1793 compress_type = zip_file.compression
1794 if arcname is None:
1795 arcname = filename
1796
1797 saved_stat = os.stat(filename)
1798
1799 try:
1800 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1801 # file to be zipped and reset it when we're done.
1802 os.chmod(filename, perms)
1803
1804 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001805 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1806 # intentional. zip stores datetimes in local time without a time zone
1807 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1808 # in the zip archive.
1809 local_epoch = datetime.datetime.fromtimestamp(0)
1810 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001811 os.utime(filename, (timestamp, timestamp))
1812
1813 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1814 finally:
1815 os.chmod(filename, saved_stat.st_mode)
1816 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1817 zipfile.ZIP64_LIMIT = saved_zip64_limit
1818
1819
Tao Bao58c1b962015-05-20 09:32:18 -07001820def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001821 compress_type=None):
1822 """Wrap zipfile.writestr() function to work around the zip64 limit.
1823
1824 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1825 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1826 when calling crc32(bytes).
1827
1828 But it still works fine to write a shorter string into a large zip file.
1829 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1830 when we know the string won't be too long.
1831 """
1832
1833 saved_zip64_limit = zipfile.ZIP64_LIMIT
1834 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1835
1836 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1837 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001838 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001839 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001840 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001841 else:
Tao Baof3282b42015-04-01 11:21:55 -07001842 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001843 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1844 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1845 # such a case (since
1846 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1847 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1848 # permission bits. We follow the logic in Python 3 to get consistent
1849 # behavior between using the two versions.
1850 if not zinfo.external_attr:
1851 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001852
1853 # If compress_type is given, it overrides the value in zinfo.
1854 if compress_type is not None:
1855 zinfo.compress_type = compress_type
1856
Tao Bao58c1b962015-05-20 09:32:18 -07001857 # If perms is given, it has a priority.
1858 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001859 # If perms doesn't set the file type, mark it as a regular file.
1860 if perms & 0o770000 == 0:
1861 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001862 zinfo.external_attr = perms << 16
1863
Tao Baof3282b42015-04-01 11:21:55 -07001864 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001865 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1866
Dan Albert8b72aef2015-03-23 19:13:21 -07001867 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001868 zipfile.ZIP64_LIMIT = saved_zip64_limit
1869
1870
Tao Bao89d7ab22017-12-14 17:05:33 -08001871def ZipDelete(zip_filename, entries):
1872 """Deletes entries from a ZIP file.
1873
1874 Since deleting entries from a ZIP file is not supported, it shells out to
1875 'zip -d'.
1876
1877 Args:
1878 zip_filename: The name of the ZIP file.
1879 entries: The name of the entry, or the list of names to be deleted.
1880
1881 Raises:
1882 AssertionError: In case of non-zero return from 'zip'.
1883 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001884 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001885 entries = [entries]
1886 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001887 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001888
1889
Tao Baof3282b42015-04-01 11:21:55 -07001890def ZipClose(zip_file):
1891 # http://b/18015246
1892 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1893 # central directory.
1894 saved_zip64_limit = zipfile.ZIP64_LIMIT
1895 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1896
1897 zip_file.close()
1898
1899 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001900
1901
1902class DeviceSpecificParams(object):
1903 module = None
1904 def __init__(self, **kwargs):
1905 """Keyword arguments to the constructor become attributes of this
1906 object, which is passed to all functions in the device-specific
1907 module."""
Tao Bao38884282019-07-10 22:20:56 -07001908 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07001909 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001910 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001911
1912 if self.module is None:
1913 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001914 if not path:
1915 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001916 try:
1917 if os.path.isdir(path):
1918 info = imp.find_module("releasetools", [path])
1919 else:
1920 d, f = os.path.split(path)
1921 b, x = os.path.splitext(f)
1922 if x == ".py":
1923 f = b
1924 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001925 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001926 self.module = imp.load_module("device_specific", *info)
1927 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001928 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001929
1930 def _DoCall(self, function_name, *args, **kwargs):
1931 """Call the named function in the device-specific module, passing
1932 the given args and kwargs. The first argument to the call will be
1933 the DeviceSpecific object itself. If there is no module, or the
1934 module does not define the function, return the value of the
1935 'default' kwarg (which itself defaults to None)."""
1936 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001937 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001938 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1939
1940 def FullOTA_Assertions(self):
1941 """Called after emitting the block of assertions at the top of a
1942 full OTA package. Implementations can add whatever additional
1943 assertions they like."""
1944 return self._DoCall("FullOTA_Assertions")
1945
Doug Zongkere5ff5902012-01-17 10:55:37 -08001946 def FullOTA_InstallBegin(self):
1947 """Called at the start of full OTA installation."""
1948 return self._DoCall("FullOTA_InstallBegin")
1949
Yifan Hong10c530d2018-12-27 17:34:18 -08001950 def FullOTA_GetBlockDifferences(self):
1951 """Called during full OTA installation and verification.
1952 Implementation should return a list of BlockDifference objects describing
1953 the update on each additional partitions.
1954 """
1955 return self._DoCall("FullOTA_GetBlockDifferences")
1956
Doug Zongker05d3dea2009-06-22 11:32:31 -07001957 def FullOTA_InstallEnd(self):
1958 """Called at the end of full OTA installation; typically this is
1959 used to install the image for the device's baseband processor."""
1960 return self._DoCall("FullOTA_InstallEnd")
1961
1962 def IncrementalOTA_Assertions(self):
1963 """Called after emitting the block of assertions at the top of an
1964 incremental OTA package. Implementations can add whatever
1965 additional assertions they like."""
1966 return self._DoCall("IncrementalOTA_Assertions")
1967
Doug Zongkere5ff5902012-01-17 10:55:37 -08001968 def IncrementalOTA_VerifyBegin(self):
1969 """Called at the start of the verification phase of incremental
1970 OTA installation; additional checks can be placed here to abort
1971 the script before any changes are made."""
1972 return self._DoCall("IncrementalOTA_VerifyBegin")
1973
Doug Zongker05d3dea2009-06-22 11:32:31 -07001974 def IncrementalOTA_VerifyEnd(self):
1975 """Called at the end of the verification phase of incremental OTA
1976 installation; additional checks can be placed here to abort the
1977 script before any changes are made."""
1978 return self._DoCall("IncrementalOTA_VerifyEnd")
1979
Doug Zongkere5ff5902012-01-17 10:55:37 -08001980 def IncrementalOTA_InstallBegin(self):
1981 """Called at the start of incremental OTA installation (after
1982 verification is complete)."""
1983 return self._DoCall("IncrementalOTA_InstallBegin")
1984
Yifan Hong10c530d2018-12-27 17:34:18 -08001985 def IncrementalOTA_GetBlockDifferences(self):
1986 """Called during incremental OTA installation and verification.
1987 Implementation should return a list of BlockDifference objects describing
1988 the update on each additional partitions.
1989 """
1990 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1991
Doug Zongker05d3dea2009-06-22 11:32:31 -07001992 def IncrementalOTA_InstallEnd(self):
1993 """Called at the end of incremental OTA installation; typically
1994 this is used to install the image for the device's baseband
1995 processor."""
1996 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001997
Tao Bao9bc6bb22015-11-09 16:58:28 -08001998 def VerifyOTA_Assertions(self):
1999 return self._DoCall("VerifyOTA_Assertions")
2000
Tao Bao76def242017-11-21 09:25:31 -08002001
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002002class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002003 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002004 self.name = name
2005 self.data = data
2006 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002007 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002008 self.sha1 = sha1(data).hexdigest()
2009
2010 @classmethod
2011 def FromLocalFile(cls, name, diskname):
2012 f = open(diskname, "rb")
2013 data = f.read()
2014 f.close()
2015 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002016
2017 def WriteToTemp(self):
2018 t = tempfile.NamedTemporaryFile()
2019 t.write(self.data)
2020 t.flush()
2021 return t
2022
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002023 def WriteToDir(self, d):
2024 with open(os.path.join(d, self.name), "wb") as fp:
2025 fp.write(self.data)
2026
Geremy Condra36bd3652014-02-06 19:45:10 -08002027 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002028 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002029
Tao Bao76def242017-11-21 09:25:31 -08002030
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002031DIFF_PROGRAM_BY_EXT = {
2032 ".gz" : "imgdiff",
2033 ".zip" : ["imgdiff", "-z"],
2034 ".jar" : ["imgdiff", "-z"],
2035 ".apk" : ["imgdiff", "-z"],
2036 ".img" : "imgdiff",
2037 }
2038
Tao Bao76def242017-11-21 09:25:31 -08002039
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002040class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002041 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002042 self.tf = tf
2043 self.sf = sf
2044 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002045 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002046
2047 def ComputePatch(self):
2048 """Compute the patch (as a string of data) needed to turn sf into
2049 tf. Returns the same tuple as GetPatch()."""
2050
2051 tf = self.tf
2052 sf = self.sf
2053
Doug Zongker24cd2802012-08-14 16:36:15 -07002054 if self.diff_program:
2055 diff_program = self.diff_program
2056 else:
2057 ext = os.path.splitext(tf.name)[1]
2058 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002059
2060 ttemp = tf.WriteToTemp()
2061 stemp = sf.WriteToTemp()
2062
2063 ext = os.path.splitext(tf.name)[1]
2064
2065 try:
2066 ptemp = tempfile.NamedTemporaryFile()
2067 if isinstance(diff_program, list):
2068 cmd = copy.copy(diff_program)
2069 else:
2070 cmd = [diff_program]
2071 cmd.append(stemp.name)
2072 cmd.append(ttemp.name)
2073 cmd.append(ptemp.name)
2074 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002075 err = []
2076 def run():
2077 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002078 if e:
2079 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002080 th = threading.Thread(target=run)
2081 th.start()
2082 th.join(timeout=300) # 5 mins
2083 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002084 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002085 p.terminate()
2086 th.join(5)
2087 if th.is_alive():
2088 p.kill()
2089 th.join()
2090
Tianjie Xua2a9f992018-01-05 15:15:54 -08002091 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002092 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002093 self.patch = None
2094 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002095 diff = ptemp.read()
2096 finally:
2097 ptemp.close()
2098 stemp.close()
2099 ttemp.close()
2100
2101 self.patch = diff
2102 return self.tf, self.sf, self.patch
2103
2104
2105 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002106 """Returns a tuple of (target_file, source_file, patch_data).
2107
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002108 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002109 computing the patch failed.
2110 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002111 return self.tf, self.sf, self.patch
2112
2113
2114def ComputeDifferences(diffs):
2115 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002116 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002117
2118 # Do the largest files first, to try and reduce the long-pole effect.
2119 by_size = [(i.tf.size, i) for i in diffs]
2120 by_size.sort(reverse=True)
2121 by_size = [i[1] for i in by_size]
2122
2123 lock = threading.Lock()
2124 diff_iter = iter(by_size) # accessed under lock
2125
2126 def worker():
2127 try:
2128 lock.acquire()
2129 for d in diff_iter:
2130 lock.release()
2131 start = time.time()
2132 d.ComputePatch()
2133 dur = time.time() - start
2134 lock.acquire()
2135
2136 tf, sf, patch = d.GetPatch()
2137 if sf.name == tf.name:
2138 name = tf.name
2139 else:
2140 name = "%s (%s)" % (tf.name, sf.name)
2141 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002142 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002143 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002144 logger.info(
2145 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2146 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002147 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002148 except Exception:
2149 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002150 raise
2151
2152 # start worker threads; wait for them all to finish.
2153 threads = [threading.Thread(target=worker)
2154 for i in range(OPTIONS.worker_threads)]
2155 for th in threads:
2156 th.start()
2157 while threads:
2158 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002159
2160
Dan Albert8b72aef2015-03-23 19:13:21 -07002161class BlockDifference(object):
2162 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002163 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002164 self.tgt = tgt
2165 self.src = src
2166 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002167 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002168 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002169
Tao Baodd2a5892015-03-12 12:32:37 -07002170 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002171 version = max(
2172 int(i) for i in
2173 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002174 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002175 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002176
Tianjie Xu41976c72019-07-03 13:57:01 -07002177 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2178 version=self.version,
2179 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002180 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002181 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002182 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002183 self.touched_src_ranges = b.touched_src_ranges
2184 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002185
Yifan Hong10c530d2018-12-27 17:34:18 -08002186 # On devices with dynamic partitions, for new partitions,
2187 # src is None but OPTIONS.source_info_dict is not.
2188 if OPTIONS.source_info_dict is None:
2189 is_dynamic_build = OPTIONS.info_dict.get(
2190 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002191 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002192 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002193 is_dynamic_build = OPTIONS.source_info_dict.get(
2194 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002195 is_dynamic_source = partition in shlex.split(
2196 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002197
Yifan Hongbb2658d2019-01-25 12:30:58 -08002198 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002199 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2200
Yifan Hongbb2658d2019-01-25 12:30:58 -08002201 # For dynamic partitions builds, check partition list in both source
2202 # and target build because new partitions may be added, and existing
2203 # partitions may be removed.
2204 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2205
Yifan Hong10c530d2018-12-27 17:34:18 -08002206 if is_dynamic:
2207 self.device = 'map_partition("%s")' % partition
2208 else:
2209 if OPTIONS.source_info_dict is None:
2210 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2211 else:
2212 _, device_path = GetTypeAndDevice("/" + partition,
2213 OPTIONS.source_info_dict)
2214 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002215
Tao Baod8d14be2016-02-04 14:26:02 -08002216 @property
2217 def required_cache(self):
2218 return self._required_cache
2219
Tao Bao76def242017-11-21 09:25:31 -08002220 def WriteScript(self, script, output_zip, progress=None,
2221 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002222 if not self.src:
2223 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002224 script.Print("Patching %s image unconditionally..." % (self.partition,))
2225 else:
2226 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002227
Dan Albert8b72aef2015-03-23 19:13:21 -07002228 if progress:
2229 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002230 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002231
2232 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002233 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002234
Tao Bao9bc6bb22015-11-09 16:58:28 -08002235 def WriteStrictVerifyScript(self, script):
2236 """Verify all the blocks in the care_map, including clobbered blocks.
2237
2238 This differs from the WriteVerifyScript() function: a) it prints different
2239 error messages; b) it doesn't allow half-way updated images to pass the
2240 verification."""
2241
2242 partition = self.partition
2243 script.Print("Verifying %s..." % (partition,))
2244 ranges = self.tgt.care_map
2245 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002246 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002247 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2248 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002249 self.device, ranges_str,
2250 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002251 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002252 script.AppendExtra("")
2253
Tao Baod522bdc2016-04-12 15:53:16 -07002254 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002255 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002256
2257 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002258 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002259 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002260
2261 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002262 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002263 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002264 ranges = self.touched_src_ranges
2265 expected_sha1 = self.touched_src_sha1
2266 else:
2267 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2268 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002269
2270 # No blocks to be checked, skipping.
2271 if not ranges:
2272 return
2273
Tao Bao5ece99d2015-05-12 11:42:31 -07002274 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002275 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002276 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002277 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2278 '"%s.patch.dat")) then' % (
2279 self.device, ranges_str, expected_sha1,
2280 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002281 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002282 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002283
Tianjie Xufc3422a2015-12-15 11:53:59 -08002284 if self.version >= 4:
2285
2286 # Bug: 21124327
2287 # When generating incrementals for the system and vendor partitions in
2288 # version 4 or newer, explicitly check the first block (which contains
2289 # the superblock) of the partition to see if it's what we expect. If
2290 # this check fails, give an explicit log message about the partition
2291 # having been remounted R/W (the most likely explanation).
2292 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002293 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002294
2295 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002296 if partition == "system":
2297 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2298 else:
2299 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002300 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002301 'ifelse (block_image_recover({device}, "{ranges}") && '
2302 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002303 'package_extract_file("{partition}.transfer.list"), '
2304 '"{partition}.new.dat", "{partition}.patch.dat"), '
2305 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002306 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002307 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002308 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002309
Tao Baodd2a5892015-03-12 12:32:37 -07002310 # Abort the OTA update. Note that the incremental OTA cannot be applied
2311 # even if it may match the checksum of the target partition.
2312 # a) If version < 3, operations like move and erase will make changes
2313 # unconditionally and damage the partition.
2314 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002315 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002316 if partition == "system":
2317 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2318 else:
2319 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2320 script.AppendExtra((
2321 'abort("E%d: %s partition has unexpected contents");\n'
2322 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002323
Yifan Hong10c530d2018-12-27 17:34:18 -08002324 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002325 partition = self.partition
2326 script.Print('Verifying the updated %s image...' % (partition,))
2327 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2328 ranges = self.tgt.care_map
2329 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002330 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002331 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002332 self.device, ranges_str,
2333 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002334
2335 # Bug: 20881595
2336 # Verify that extended blocks are really zeroed out.
2337 if self.tgt.extended:
2338 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002339 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002340 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002341 self.device, ranges_str,
2342 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002343 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002344 if partition == "system":
2345 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2346 else:
2347 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002348 script.AppendExtra(
2349 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002350 ' abort("E%d: %s partition has unexpected non-zero contents after '
2351 'OTA update");\n'
2352 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002353 else:
2354 script.Print('Verified the updated %s image.' % (partition,))
2355
Tianjie Xu209db462016-05-24 17:34:52 -07002356 if partition == "system":
2357 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2358 else:
2359 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2360
Tao Bao5fcaaef2015-06-01 13:40:49 -07002361 script.AppendExtra(
2362 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002363 ' abort("E%d: %s partition has unexpected contents after OTA '
2364 'update");\n'
2365 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002366
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002367 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002368 ZipWrite(output_zip,
2369 '{}.transfer.list'.format(self.path),
2370 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002371
Tao Bao76def242017-11-21 09:25:31 -08002372 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2373 # its size. Quailty 9 almost triples the compression time but doesn't
2374 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002375 # zip | brotli(quality 6) | brotli(quality 9)
2376 # compressed_size: 942M | 869M (~8% reduced) | 854M
2377 # compression_time: 75s | 265s | 719s
2378 # decompression_time: 15s | 25s | 25s
2379
2380 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002381 brotli_cmd = ['brotli', '--quality=6',
2382 '--output={}.new.dat.br'.format(self.path),
2383 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002384 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002385 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002386
2387 new_data_name = '{}.new.dat.br'.format(self.partition)
2388 ZipWrite(output_zip,
2389 '{}.new.dat.br'.format(self.path),
2390 new_data_name,
2391 compress_type=zipfile.ZIP_STORED)
2392 else:
2393 new_data_name = '{}.new.dat'.format(self.partition)
2394 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2395
Dan Albert8e0178d2015-01-27 15:53:15 -08002396 ZipWrite(output_zip,
2397 '{}.patch.dat'.format(self.path),
2398 '{}.patch.dat'.format(self.partition),
2399 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002400
Tianjie Xu209db462016-05-24 17:34:52 -07002401 if self.partition == "system":
2402 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2403 else:
2404 code = ErrorCode.VENDOR_UPDATE_FAILURE
2405
Yifan Hong10c530d2018-12-27 17:34:18 -08002406 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002407 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002408 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002409 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002410 device=self.device, partition=self.partition,
2411 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002412 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002413
Dan Albert8b72aef2015-03-23 19:13:21 -07002414 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002415 data = source.ReadRangeSet(ranges)
2416 ctx = sha1()
2417
2418 for p in data:
2419 ctx.update(p)
2420
2421 return ctx.hexdigest()
2422
Tao Baoe9b61912015-07-09 17:37:49 -07002423 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2424 """Return the hash value for all zero blocks."""
2425 zero_block = '\x00' * 4096
2426 ctx = sha1()
2427 for _ in range(num_blocks):
2428 ctx.update(zero_block)
2429
2430 return ctx.hexdigest()
2431
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002432
Tianjie Xu41976c72019-07-03 13:57:01 -07002433# Expose these two classes to support vendor-specific scripts
2434DataImage = images.DataImage
2435EmptyImage = images.EmptyImage
2436
Tao Bao76def242017-11-21 09:25:31 -08002437
Doug Zongker96a57e72010-09-26 14:57:41 -07002438# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002439PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002440 "ext4": "EMMC",
2441 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002442 "f2fs": "EMMC",
2443 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002444}
Doug Zongker96a57e72010-09-26 14:57:41 -07002445
Tao Bao76def242017-11-21 09:25:31 -08002446
Doug Zongker96a57e72010-09-26 14:57:41 -07002447def GetTypeAndDevice(mount_point, info):
2448 fstab = info["fstab"]
2449 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002450 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2451 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002452 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002453 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002454
2455
2456def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002457 """Parses and converts a PEM-encoded certificate into DER-encoded.
2458
2459 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2460
2461 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002462 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002463 """
2464 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002465 save = False
2466 for line in data.split("\n"):
2467 if "--END CERTIFICATE--" in line:
2468 break
2469 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002470 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002471 if "--BEGIN CERTIFICATE--" in line:
2472 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002473 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002474 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002475
Tao Bao04e1f012018-02-04 12:13:35 -08002476
2477def ExtractPublicKey(cert):
2478 """Extracts the public key (PEM-encoded) from the given certificate file.
2479
2480 Args:
2481 cert: The certificate filename.
2482
2483 Returns:
2484 The public key string.
2485
2486 Raises:
2487 AssertionError: On non-zero return from 'openssl'.
2488 """
2489 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2490 # While openssl 1.1 writes the key into the given filename followed by '-out',
2491 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2492 # stdout instead.
2493 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2494 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2495 pubkey, stderrdata = proc.communicate()
2496 assert proc.returncode == 0, \
2497 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2498 return pubkey
2499
2500
Tao Bao1ac886e2019-06-26 11:58:22 -07002501def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002502 """Extracts the AVB public key from the given public or private key.
2503
2504 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002505 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002506 key: The input key file, which should be PEM-encoded public or private key.
2507
2508 Returns:
2509 The path to the extracted AVB public key file.
2510 """
2511 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2512 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002513 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002514 return output
2515
2516
Doug Zongker412c02f2014-02-13 10:58:24 -08002517def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2518 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002519 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002520
Tao Bao6d5d6232018-03-09 17:04:42 -08002521 Most of the space in the boot and recovery images is just the kernel, which is
2522 identical for the two, so the resulting patch should be efficient. Add it to
2523 the output zip, along with a shell script that is run from init.rc on first
2524 boot to actually do the patching and install the new recovery image.
2525
2526 Args:
2527 input_dir: The top-level input directory of the target-files.zip.
2528 output_sink: The callback function that writes the result.
2529 recovery_img: File object for the recovery image.
2530 boot_img: File objects for the boot image.
2531 info_dict: A dict returned by common.LoadInfoDict() on the input
2532 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002533 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002534 if info_dict is None:
2535 info_dict = OPTIONS.info_dict
2536
Tao Bao6d5d6232018-03-09 17:04:42 -08002537 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002538 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2539
2540 if board_uses_vendorimage:
2541 # In this case, the output sink is rooted at VENDOR
2542 recovery_img_path = "etc/recovery.img"
2543 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2544 sh_dir = "bin"
2545 else:
2546 # In this case the output sink is rooted at SYSTEM
2547 recovery_img_path = "vendor/etc/recovery.img"
2548 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2549 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002550
Tao Baof2cffbd2015-07-22 12:33:18 -07002551 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002552 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002553
2554 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002555 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002556 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002557 # With system-root-image, boot and recovery images will have mismatching
2558 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2559 # to handle such a case.
2560 if system_root_image:
2561 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002562 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002563 assert not os.path.exists(path)
2564 else:
2565 diff_program = ["imgdiff"]
2566 if os.path.exists(path):
2567 diff_program.append("-b")
2568 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002569 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002570 else:
2571 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002572
2573 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2574 _, _, patch = d.ComputePatch()
2575 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002576
Dan Albertebb19aa2015-03-27 19:11:53 -07002577 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002578 # The following GetTypeAndDevice()s need to use the path in the target
2579 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002580 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2581 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2582 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002583 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002584
Tao Baof2cffbd2015-07-22 12:33:18 -07002585 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002586
2587 # Note that we use /vendor to refer to the recovery resources. This will
2588 # work for a separate vendor partition mounted at /vendor or a
2589 # /system/vendor subdirectory on the system partition, for which init will
2590 # create a symlink from /vendor to /system/vendor.
2591
2592 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002593if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2594 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002595 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002596 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2597 log -t recovery "Installing new recovery image: succeeded" || \\
2598 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002599else
2600 log -t recovery "Recovery image already installed"
2601fi
2602""" % {'type': recovery_type,
2603 'device': recovery_device,
2604 'sha1': recovery_img.sha1,
2605 'size': recovery_img.size}
2606 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002607 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002608if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2609 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002610 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002611 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2612 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2613 log -t recovery "Installing new recovery image: succeeded" || \\
2614 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002615else
2616 log -t recovery "Recovery image already installed"
2617fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002618""" % {'boot_size': boot_img.size,
2619 'boot_sha1': boot_img.sha1,
2620 'recovery_size': recovery_img.size,
2621 'recovery_sha1': recovery_img.sha1,
2622 'boot_type': boot_type,
2623 'boot_device': boot_device,
2624 'recovery_type': recovery_type,
2625 'recovery_device': recovery_device,
2626 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002627
Bill Peckhame868aec2019-09-17 17:06:47 -07002628 # The install script location moved from /system/etc to /system/bin in the L
2629 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2630 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002631
Tao Bao32fcdab2018-10-12 10:30:39 -07002632 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002633
Tao Baoda30cfa2017-12-01 16:19:46 -08002634 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002635
2636
2637class DynamicPartitionUpdate(object):
2638 def __init__(self, src_group=None, tgt_group=None, progress=None,
2639 block_difference=None):
2640 self.src_group = src_group
2641 self.tgt_group = tgt_group
2642 self.progress = progress
2643 self.block_difference = block_difference
2644
2645 @property
2646 def src_size(self):
2647 if not self.block_difference:
2648 return 0
2649 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2650
2651 @property
2652 def tgt_size(self):
2653 if not self.block_difference:
2654 return 0
2655 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2656
2657 @staticmethod
2658 def _GetSparseImageSize(img):
2659 if not img:
2660 return 0
2661 return img.blocksize * img.total_blocks
2662
2663
2664class DynamicGroupUpdate(object):
2665 def __init__(self, src_size=None, tgt_size=None):
2666 # None: group does not exist. 0: no size limits.
2667 self.src_size = src_size
2668 self.tgt_size = tgt_size
2669
2670
2671class DynamicPartitionsDifference(object):
2672 def __init__(self, info_dict, block_diffs, progress_dict=None,
2673 source_info_dict=None):
2674 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002675 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002676
2677 self._remove_all_before_apply = False
2678 if source_info_dict is None:
2679 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002680 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002681
Tao Baof1113e92019-06-18 12:10:14 -07002682 block_diff_dict = collections.OrderedDict(
2683 [(e.partition, e) for e in block_diffs])
2684
Yifan Hong10c530d2018-12-27 17:34:18 -08002685 assert len(block_diff_dict) == len(block_diffs), \
2686 "Duplicated BlockDifference object for {}".format(
2687 [partition for partition, count in
2688 collections.Counter(e.partition for e in block_diffs).items()
2689 if count > 1])
2690
Yifan Hong79997e52019-01-23 16:56:19 -08002691 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002692
2693 for p, block_diff in block_diff_dict.items():
2694 self._partition_updates[p] = DynamicPartitionUpdate()
2695 self._partition_updates[p].block_difference = block_diff
2696
2697 for p, progress in progress_dict.items():
2698 if p in self._partition_updates:
2699 self._partition_updates[p].progress = progress
2700
2701 tgt_groups = shlex.split(info_dict.get(
2702 "super_partition_groups", "").strip())
2703 src_groups = shlex.split(source_info_dict.get(
2704 "super_partition_groups", "").strip())
2705
2706 for g in tgt_groups:
2707 for p in shlex.split(info_dict.get(
2708 "super_%s_partition_list" % g, "").strip()):
2709 assert p in self._partition_updates, \
2710 "{} is in target super_{}_partition_list but no BlockDifference " \
2711 "object is provided.".format(p, g)
2712 self._partition_updates[p].tgt_group = g
2713
2714 for g in src_groups:
2715 for p in shlex.split(source_info_dict.get(
2716 "super_%s_partition_list" % g, "").strip()):
2717 assert p in self._partition_updates, \
2718 "{} is in source super_{}_partition_list but no BlockDifference " \
2719 "object is provided.".format(p, g)
2720 self._partition_updates[p].src_group = g
2721
Yifan Hong45433e42019-01-18 13:55:25 -08002722 target_dynamic_partitions = set(shlex.split(info_dict.get(
2723 "dynamic_partition_list", "").strip()))
2724 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2725 if u.tgt_size)
2726 assert block_diffs_with_target == target_dynamic_partitions, \
2727 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2728 list(target_dynamic_partitions), list(block_diffs_with_target))
2729
2730 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2731 "dynamic_partition_list", "").strip()))
2732 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2733 if u.src_size)
2734 assert block_diffs_with_source == source_dynamic_partitions, \
2735 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2736 list(source_dynamic_partitions), list(block_diffs_with_source))
2737
Yifan Hong10c530d2018-12-27 17:34:18 -08002738 if self._partition_updates:
2739 logger.info("Updating dynamic partitions %s",
2740 self._partition_updates.keys())
2741
Yifan Hong79997e52019-01-23 16:56:19 -08002742 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002743
2744 for g in tgt_groups:
2745 self._group_updates[g] = DynamicGroupUpdate()
2746 self._group_updates[g].tgt_size = int(info_dict.get(
2747 "super_%s_group_size" % g, "0").strip())
2748
2749 for g in src_groups:
2750 if g not in self._group_updates:
2751 self._group_updates[g] = DynamicGroupUpdate()
2752 self._group_updates[g].src_size = int(source_info_dict.get(
2753 "super_%s_group_size" % g, "0").strip())
2754
2755 self._Compute()
2756
2757 def WriteScript(self, script, output_zip, write_verify_script=False):
2758 script.Comment('--- Start patching dynamic partitions ---')
2759 for p, u in self._partition_updates.items():
2760 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2761 script.Comment('Patch partition %s' % p)
2762 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2763 write_verify_script=False)
2764
2765 op_list_path = MakeTempFile()
2766 with open(op_list_path, 'w') as f:
2767 for line in self._op_list:
2768 f.write('{}\n'.format(line))
2769
2770 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2771
2772 script.Comment('Update dynamic partition metadata')
2773 script.AppendExtra('assert(update_dynamic_partitions('
2774 'package_extract_file("dynamic_partitions_op_list")));')
2775
2776 if write_verify_script:
2777 for p, u in self._partition_updates.items():
2778 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2779 u.block_difference.WritePostInstallVerifyScript(script)
2780 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2781
2782 for p, u in self._partition_updates.items():
2783 if u.tgt_size and u.src_size <= u.tgt_size:
2784 script.Comment('Patch partition %s' % p)
2785 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2786 write_verify_script=write_verify_script)
2787 if write_verify_script:
2788 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2789
2790 script.Comment('--- End patching dynamic partitions ---')
2791
2792 def _Compute(self):
2793 self._op_list = list()
2794
2795 def append(line):
2796 self._op_list.append(line)
2797
2798 def comment(line):
2799 self._op_list.append("# %s" % line)
2800
2801 if self._remove_all_before_apply:
2802 comment('Remove all existing dynamic partitions and groups before '
2803 'applying full OTA')
2804 append('remove_all_groups')
2805
2806 for p, u in self._partition_updates.items():
2807 if u.src_group and not u.tgt_group:
2808 append('remove %s' % p)
2809
2810 for p, u in self._partition_updates.items():
2811 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2812 comment('Move partition %s from %s to default' % (p, u.src_group))
2813 append('move %s default' % p)
2814
2815 for p, u in self._partition_updates.items():
2816 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2817 comment('Shrink partition %s from %d to %d' %
2818 (p, u.src_size, u.tgt_size))
2819 append('resize %s %s' % (p, u.tgt_size))
2820
2821 for g, u in self._group_updates.items():
2822 if u.src_size is not None and u.tgt_size is None:
2823 append('remove_group %s' % g)
2824 if (u.src_size is not None and u.tgt_size is not None and
2825 u.src_size > u.tgt_size):
2826 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2827 append('resize_group %s %d' % (g, u.tgt_size))
2828
2829 for g, u in self._group_updates.items():
2830 if u.src_size is None and u.tgt_size is not None:
2831 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2832 append('add_group %s %d' % (g, u.tgt_size))
2833 if (u.src_size is not None and u.tgt_size is not None and
2834 u.src_size < u.tgt_size):
2835 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2836 append('resize_group %s %d' % (g, u.tgt_size))
2837
2838 for p, u in self._partition_updates.items():
2839 if u.tgt_group and not u.src_group:
2840 comment('Add partition %s to group %s' % (p, u.tgt_group))
2841 append('add %s %s' % (p, u.tgt_group))
2842
2843 for p, u in self._partition_updates.items():
2844 if u.tgt_size and u.src_size < u.tgt_size:
2845 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2846 append('resize %s %d' % (p, u.tgt_size))
2847
2848 for p, u in self._partition_updates.items():
2849 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2850 comment('Move partition %s from default to %s' %
2851 (p, u.tgt_group))
2852 append('move %s %s' % (p, u.tgt_group))