blob: 384d1bff18dd1de11dec9c8dce012214dc20767d [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")
619 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
620 if key_path and algorithm:
621 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700622 avb_salt = OPTIONS.info_dict.get("avb_salt")
623 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700624 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700625 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800626
627
Daniel Norman276f0622019-07-26 14:13:51 -0700628def GetAvbPartitionArg(partition, image, info_dict = None):
629 """Returns the VBMeta arguments for partition.
630
631 It sets up the VBMeta argument by including the partition descriptor from the
632 given 'image', or by configuring the partition as a chained partition.
633
634 Args:
635 partition: The name of the partition (e.g. "system").
636 image: The path to the partition image.
637 info_dict: A dict returned by common.LoadInfoDict(). Will use
638 OPTIONS.info_dict if None has been given.
639
640 Returns:
641 A list of VBMeta arguments.
642 """
643 if info_dict is None:
644 info_dict = OPTIONS.info_dict
645
646 # Check if chain partition is used.
647 key_path = info_dict.get("avb_" + partition + "_key_path")
648 if key_path:
649 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
650 return ["--chain_partition", chained_partition_arg]
651 else:
652 return ["--include_descriptors_from_image", image]
653
654
Tao Bao02a08592018-07-22 12:40:45 -0700655def GetAvbChainedPartitionArg(partition, info_dict, key=None):
656 """Constructs and returns the arg to build or verify a chained partition.
657
658 Args:
659 partition: The partition name.
660 info_dict: The info dict to look up the key info and rollback index
661 location.
662 key: The key to be used for building or verifying the partition. Defaults to
663 the key listed in info_dict.
664
665 Returns:
666 A string of form "partition:rollback_index_location:key" that can be used to
667 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700668 """
669 if key is None:
670 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao1ac886e2019-06-26 11:58:22 -0700671 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700672 rollback_index_location = info_dict[
673 "avb_" + partition + "_rollback_index_location"]
674 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
675
676
Daniel Norman276f0622019-07-26 14:13:51 -0700677def BuildVBMeta(image_path, partitions, name, needed_partitions):
678 """Creates a VBMeta image.
679
680 It generates the requested VBMeta image. The requested image could be for
681 top-level or chained VBMeta image, which is determined based on the name.
682
683 Args:
684 image_path: The output path for the new VBMeta image.
685 partitions: A dict that's keyed by partition names with image paths as
686 values. Only valid partition names are accepted, as listed in
687 common.AVB_PARTITIONS.
688 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
689 needed_partitions: Partitions whose descriptors should be included into the
690 generated VBMeta image.
691
692 Raises:
693 AssertionError: On invalid input args.
694 """
695 avbtool = OPTIONS.info_dict["avb_avbtool"]
696 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
697 AppendAVBSigningArgs(cmd, name)
698
699 for partition, path in partitions.items():
700 if partition not in needed_partitions:
701 continue
702 assert (partition in AVB_PARTITIONS or
703 partition in AVB_VBMETA_PARTITIONS), \
704 'Unknown partition: {}'.format(partition)
705 assert os.path.exists(path), \
706 'Failed to find {} for {}'.format(path, partition)
707 cmd.extend(GetAvbPartitionArg(partition, path))
708
709 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
710 if args and args.strip():
711 split_args = shlex.split(args)
712 for index, arg in enumerate(split_args[:-1]):
713 # Sanity check that the image file exists. Some images might be defined
714 # as a path relative to source tree, which may not be available at the
715 # same location when running this script (we have the input target_files
716 # zip only). For such cases, we additionally scan other locations (e.g.
717 # IMAGES/, RADIO/, etc) before bailing out.
718 if arg == '--include_descriptors_from_image':
719 image_path = split_args[index + 1]
720 if os.path.exists(image_path):
721 continue
722 found = False
723 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
724 alt_path = os.path.join(
725 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
726 if os.path.exists(alt_path):
727 split_args[index + 1] = alt_path
728 found = True
729 break
730 assert found, 'Failed to find {}'.format(image_path)
731 cmd.extend(split_args)
732
733 RunAndCheckOutput(cmd)
734
735
Steve Mucklee1b10862019-07-10 10:49:37 -0700736def _MakeRamdisk(sourcedir, fs_config_file=None):
737 ramdisk_img = tempfile.NamedTemporaryFile()
738
739 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
740 cmd = ["mkbootfs", "-f", fs_config_file,
741 os.path.join(sourcedir, "RAMDISK")]
742 else:
743 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
744 p1 = Run(cmd, stdout=subprocess.PIPE)
745 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
746
747 p2.wait()
748 p1.wait()
749 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
750 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
751
752 return ramdisk_img
753
754
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700755def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800756 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700757 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700758
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700759 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800760 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
761 we are building a two-step special image (i.e. building a recovery image to
762 be loaded into /boot in two-step OTAs).
763
764 Return the image data, or None if sourcedir does not appear to contains files
765 for building the requested image.
766 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700767
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700768 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
769 return None
770
771 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700772 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700773
Doug Zongkerd5131602012-08-02 14:46:42 -0700774 if info_dict is None:
775 info_dict = OPTIONS.info_dict
776
Doug Zongkereef39442009-04-02 12:14:19 -0700777 img = tempfile.NamedTemporaryFile()
778
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700779 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -0700780 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -0700781
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800782 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
783 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
784
785 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700786
Benoit Fradina45a8682014-07-14 21:00:43 +0200787 fn = os.path.join(sourcedir, "second")
788 if os.access(fn, os.F_OK):
789 cmd.append("--second")
790 cmd.append(fn)
791
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800792 fn = os.path.join(sourcedir, "dtb")
793 if os.access(fn, os.F_OK):
794 cmd.append("--dtb")
795 cmd.append(fn)
796
Doug Zongker171f1cd2009-06-15 22:36:37 -0700797 fn = os.path.join(sourcedir, "cmdline")
798 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700799 cmd.append("--cmdline")
800 cmd.append(open(fn).read().rstrip("\n"))
801
802 fn = os.path.join(sourcedir, "base")
803 if os.access(fn, os.F_OK):
804 cmd.append("--base")
805 cmd.append(open(fn).read().rstrip("\n"))
806
Ying Wang4de6b5b2010-08-25 14:29:34 -0700807 fn = os.path.join(sourcedir, "pagesize")
808 if os.access(fn, os.F_OK):
809 cmd.append("--pagesize")
810 cmd.append(open(fn).read().rstrip("\n"))
811
Tao Bao76def242017-11-21 09:25:31 -0800812 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700813 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700814 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700815
Tao Bao76def242017-11-21 09:25:31 -0800816 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000817 if args and args.strip():
818 cmd.extend(shlex.split(args))
819
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700820 if has_ramdisk:
821 cmd.extend(["--ramdisk", ramdisk_img.name])
822
Tao Baod95e9fd2015-03-29 23:07:41 -0700823 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800824 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700825 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700826 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700827 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700828 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700829
Tao Baobf70c312017-07-11 17:27:55 -0700830 # "boot" or "recovery", without extension.
831 partition_name = os.path.basename(sourcedir).lower()
832
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800833 if partition_name == "recovery":
834 if info_dict.get("include_recovery_dtbo") == "true":
835 fn = os.path.join(sourcedir, "recovery_dtbo")
836 cmd.extend(["--recovery_dtbo", fn])
837 if info_dict.get("include_recovery_acpio") == "true":
838 fn = os.path.join(sourcedir, "recovery_acpio")
839 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700840
Tao Bao986ee862018-10-04 15:46:16 -0700841 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700842
Tao Bao76def242017-11-21 09:25:31 -0800843 if (info_dict.get("boot_signer") == "true" and
844 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800845 # Hard-code the path as "/boot" for two-step special recovery image (which
846 # will be loaded into /boot during the two-step OTA).
847 if two_step_image:
848 path = "/boot"
849 else:
Tao Baobf70c312017-07-11 17:27:55 -0700850 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700851 cmd = [OPTIONS.boot_signer_path]
852 cmd.extend(OPTIONS.boot_signer_args)
853 cmd.extend([path, img.name,
854 info_dict["verity_key"] + ".pk8",
855 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700856 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700857
Tao Baod95e9fd2015-03-29 23:07:41 -0700858 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800859 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700860 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700861 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800862 # We have switched from the prebuilt futility binary to using the tool
863 # (futility-host) built from the source. Override the setting in the old
864 # TF.zip.
865 futility = info_dict["futility"]
866 if futility.startswith("prebuilts/"):
867 futility = "futility-host"
868 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700869 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700870 info_dict["vboot_key"] + ".vbprivk",
871 info_dict["vboot_subkey"] + ".vbprivk",
872 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700873 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700874 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700875
Tao Baof3282b42015-04-01 11:21:55 -0700876 # Clean up the temp files.
877 img_unsigned.close()
878 img_keyblock.close()
879
David Zeuthen8fecb282017-12-01 16:24:01 -0500880 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800881 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700882 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500883 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400884 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700885 "--partition_size", str(part_size), "--partition_name",
886 partition_name]
887 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500888 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400889 if args and args.strip():
890 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700891 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500892
893 img.seek(os.SEEK_SET, 0)
894 data = img.read()
895
896 if has_ramdisk:
897 ramdisk_img.close()
898 img.close()
899
900 return data
901
902
Doug Zongkerd5131602012-08-02 14:46:42 -0700903def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800904 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700905 """Return a File object with the desired bootable image.
906
907 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
908 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
909 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700910
Doug Zongker55d93282011-01-25 17:03:34 -0800911 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
912 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700913 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800914 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700915
916 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
917 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700918 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700919 return File.FromLocalFile(name, prebuilt_path)
920
Tao Bao32fcdab2018-10-12 10:30:39 -0700921 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700922
923 if info_dict is None:
924 info_dict = OPTIONS.info_dict
925
926 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800927 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
928 # for recovery.
929 has_ramdisk = (info_dict.get("system_root_image") != "true" or
930 prebuilt_name != "boot.img" or
931 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700932
Doug Zongker6f1d0312014-08-22 08:07:12 -0700933 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400934 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
935 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800936 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700937 if data:
938 return File(name, data)
939 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800940
Doug Zongkereef39442009-04-02 12:14:19 -0700941
Steve Mucklee1b10862019-07-10 10:49:37 -0700942def _BuildVendorBootImage(sourcedir, info_dict=None):
943 """Build a vendor boot image from the specified sourcedir.
944
945 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
946 turn them into a vendor boot image.
947
948 Return the image data, or None if sourcedir does not appear to contains files
949 for building the requested image.
950 """
951
952 if info_dict is None:
953 info_dict = OPTIONS.info_dict
954
955 img = tempfile.NamedTemporaryFile()
956
957 ramdisk_img = _MakeRamdisk(sourcedir)
958
959 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
960 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
961
962 cmd = [mkbootimg]
963
964 fn = os.path.join(sourcedir, "dtb")
965 if os.access(fn, os.F_OK):
966 cmd.append("--dtb")
967 cmd.append(fn)
968
969 fn = os.path.join(sourcedir, "vendor_cmdline")
970 if os.access(fn, os.F_OK):
971 cmd.append("--vendor_cmdline")
972 cmd.append(open(fn).read().rstrip("\n"))
973
974 fn = os.path.join(sourcedir, "base")
975 if os.access(fn, os.F_OK):
976 cmd.append("--base")
977 cmd.append(open(fn).read().rstrip("\n"))
978
979 fn = os.path.join(sourcedir, "pagesize")
980 if os.access(fn, os.F_OK):
981 cmd.append("--pagesize")
982 cmd.append(open(fn).read().rstrip("\n"))
983
984 args = info_dict.get("mkbootimg_args")
985 if args and args.strip():
986 cmd.extend(shlex.split(args))
987
988 args = info_dict.get("mkbootimg_version_args")
989 if args and args.strip():
990 cmd.extend(shlex.split(args))
991
992 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
993 cmd.extend(["--vendor_boot", img.name])
994
995 RunAndCheckOutput(cmd)
996
997 # AVB: if enabled, calculate and add hash.
998 if info_dict.get("avb_enable") == "true":
999 avbtool = info_dict["avb_avbtool"]
1000 part_size = info_dict["vendor_boot_size"]
1001 cmd = [avbtool, "add_hash_footer", "--image", img.name,
1002 "--partition_size", str(part_size), "--partition_name vendor_boot"]
1003 AppendAVBSigningArgs(cmd, "vendor_boot")
1004 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1005 if args and args.strip():
1006 cmd.extend(shlex.split(args))
1007 RunAndCheckOutput(cmd)
1008
1009 img.seek(os.SEEK_SET, 0)
1010 data = img.read()
1011
1012 ramdisk_img.close()
1013 img.close()
1014
1015 return data
1016
1017
1018def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1019 info_dict=None):
1020 """Return a File object with the desired vendor boot image.
1021
1022 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1023 the source files in 'unpack_dir'/'tree_subdir'."""
1024
1025 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1026 if os.path.exists(prebuilt_path):
1027 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1028 return File.FromLocalFile(name, prebuilt_path)
1029
1030 logger.info("building image from target_files %s...", tree_subdir)
1031
1032 if info_dict is None:
1033 info_dict = OPTIONS.info_dict
1034
1035 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1036 if data:
1037 return File(name, data)
1038 return None
1039
1040
Narayan Kamatha07bf042017-08-14 14:49:21 +01001041def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001042 """Gunzips the given gzip compressed file to a given output file."""
1043 with gzip.open(in_filename, "rb") as in_file, \
1044 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001045 shutil.copyfileobj(in_file, out_file)
1046
1047
Tao Bao0ff15de2019-03-20 11:26:06 -07001048def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001049 """Unzips the archive to the given directory.
1050
1051 Args:
1052 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001053 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001054 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1055 archvie. Non-matching patterns will be filtered out. If there's no match
1056 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001057 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001058 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001059 if patterns is not None:
1060 # Filter out non-matching patterns. unzip will complain otherwise.
1061 with zipfile.ZipFile(filename) as input_zip:
1062 names = input_zip.namelist()
1063 filtered = [
1064 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1065
1066 # There isn't any matching files. Don't unzip anything.
1067 if not filtered:
1068 return
1069 cmd.extend(filtered)
1070
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001071 RunAndCheckOutput(cmd)
1072
1073
Doug Zongker75f17362009-12-08 13:46:44 -08001074def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001075 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001076
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001077 Args:
1078 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1079 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1080
1081 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1082 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001083
Tao Bao1c830bf2017-12-25 10:43:47 -08001084 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001085 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001086 """
Doug Zongkereef39442009-04-02 12:14:19 -07001087
Tao Bao1c830bf2017-12-25 10:43:47 -08001088 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001089 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1090 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001091 UnzipToDir(m.group(1), tmp, pattern)
1092 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001093 filename = m.group(1)
1094 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001095 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001096
Tao Baodba59ee2018-01-09 13:21:02 -08001097 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001098
1099
Yifan Hong8a66a712019-04-04 15:37:57 -07001100def GetUserImage(which, tmpdir, input_zip,
1101 info_dict=None,
1102 allow_shared_blocks=None,
1103 hashtree_info_generator=None,
1104 reset_file_map=False):
1105 """Returns an Image object suitable for passing to BlockImageDiff.
1106
1107 This function loads the specified image from the given path. If the specified
1108 image is sparse, it also performs additional processing for OTA purpose. For
1109 example, it always adds block 0 to clobbered blocks list. It also detects
1110 files that cannot be reconstructed from the block list, for whom we should
1111 avoid applying imgdiff.
1112
1113 Args:
1114 which: The partition name.
1115 tmpdir: The directory that contains the prebuilt image and block map file.
1116 input_zip: The target-files ZIP archive.
1117 info_dict: The dict to be looked up for relevant info.
1118 allow_shared_blocks: If image is sparse, whether having shared blocks is
1119 allowed. If none, it is looked up from info_dict.
1120 hashtree_info_generator: If present and image is sparse, generates the
1121 hashtree_info for this sparse image.
1122 reset_file_map: If true and image is sparse, reset file map before returning
1123 the image.
1124 Returns:
1125 A Image object. If it is a sparse image and reset_file_map is False, the
1126 image will have file_map info loaded.
1127 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001128 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001129 info_dict = LoadInfoDict(input_zip)
1130
1131 is_sparse = info_dict.get("extfs_sparse_flag")
1132
1133 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1134 # shared blocks (i.e. some blocks will show up in multiple files' block
1135 # list). We can only allocate such shared blocks to the first "owner", and
1136 # disable imgdiff for all later occurrences.
1137 if allow_shared_blocks is None:
1138 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1139
1140 if is_sparse:
1141 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1142 hashtree_info_generator)
1143 if reset_file_map:
1144 img.ResetFileMap()
1145 return img
1146 else:
1147 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1148
1149
1150def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1151 """Returns a Image object suitable for passing to BlockImageDiff.
1152
1153 This function loads the specified non-sparse image from the given path.
1154
1155 Args:
1156 which: The partition name.
1157 tmpdir: The directory that contains the prebuilt image and block map file.
1158 Returns:
1159 A Image object.
1160 """
1161 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1162 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1163
1164 # The image and map files must have been created prior to calling
1165 # ota_from_target_files.py (since LMP).
1166 assert os.path.exists(path) and os.path.exists(mappath)
1167
Tianjie Xu41976c72019-07-03 13:57:01 -07001168 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1169
Yifan Hong8a66a712019-04-04 15:37:57 -07001170
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001171def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1172 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001173 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1174
1175 This function loads the specified sparse image from the given path, and
1176 performs additional processing for OTA purpose. For example, it always adds
1177 block 0 to clobbered blocks list. It also detects files that cannot be
1178 reconstructed from the block list, for whom we should avoid applying imgdiff.
1179
1180 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001181 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001182 tmpdir: The directory that contains the prebuilt image and block map file.
1183 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001184 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001185 hashtree_info_generator: If present, generates the hashtree_info for this
1186 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001187 Returns:
1188 A SparseImage object, with file_map info loaded.
1189 """
Tao Baoc765cca2018-01-31 17:32:40 -08001190 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1191 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1192
1193 # The image and map files must have been created prior to calling
1194 # ota_from_target_files.py (since LMP).
1195 assert os.path.exists(path) and os.path.exists(mappath)
1196
1197 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1198 # it to clobbered_blocks so that it will be written to the target
1199 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1200 clobbered_blocks = "0"
1201
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001202 image = sparse_img.SparseImage(
1203 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1204 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001205
1206 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1207 # if they contain all zeros. We can't reconstruct such a file from its block
1208 # list. Tag such entries accordingly. (Bug: 65213616)
1209 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001210 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001211 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001212 continue
1213
Tom Cherryd14b8952018-08-09 14:26:00 -07001214 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1215 # filename listed in system.map may contain an additional leading slash
1216 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1217 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001218 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001219
Tom Cherryd14b8952018-08-09 14:26:00 -07001220 # Special handling another case, where files not under /system
1221 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001222 if which == 'system' and not arcname.startswith('SYSTEM'):
1223 arcname = 'ROOT/' + arcname
1224
1225 assert arcname in input_zip.namelist(), \
1226 "Failed to find the ZIP entry for {}".format(entry)
1227
Tao Baoc765cca2018-01-31 17:32:40 -08001228 info = input_zip.getinfo(arcname)
1229 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001230
1231 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001232 # image, check the original block list to determine its completeness. Note
1233 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001234 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001235 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001236
Tao Baoc765cca2018-01-31 17:32:40 -08001237 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1238 ranges.extra['incomplete'] = True
1239
1240 return image
1241
1242
Doug Zongkereef39442009-04-02 12:14:19 -07001243def GetKeyPasswords(keylist):
1244 """Given a list of keys, prompt the user to enter passwords for
1245 those which require them. Return a {key: password} dict. password
1246 will be None if the key has no password."""
1247
Doug Zongker8ce7c252009-05-22 13:34:54 -07001248 no_passwords = []
1249 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001250 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001251 devnull = open("/dev/null", "w+b")
1252 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001253 # We don't need a password for things that aren't really keys.
1254 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001255 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001256 continue
1257
T.R. Fullhart37e10522013-03-18 10:31:26 -07001258 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001259 "-inform", "DER", "-nocrypt"],
1260 stdin=devnull.fileno(),
1261 stdout=devnull.fileno(),
1262 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001263 p.communicate()
1264 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001265 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001266 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001267 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001268 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1269 "-inform", "DER", "-passin", "pass:"],
1270 stdin=devnull.fileno(),
1271 stdout=devnull.fileno(),
1272 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001273 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001274 if p.returncode == 0:
1275 # Encrypted key with empty string as password.
1276 key_passwords[k] = ''
1277 elif stderr.startswith('Error decrypting key'):
1278 # Definitely encrypted key.
1279 # It would have said "Error reading key" if it didn't parse correctly.
1280 need_passwords.append(k)
1281 else:
1282 # Potentially, a type of key that openssl doesn't understand.
1283 # We'll let the routines in signapk.jar handle it.
1284 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001285 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001286
T.R. Fullhart37e10522013-03-18 10:31:26 -07001287 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001288 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001289 return key_passwords
1290
1291
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001292def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001293 """Gets the minSdkVersion declared in the APK.
1294
1295 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1296 This can be both a decimal number (API Level) or a codename.
1297
1298 Args:
1299 apk_name: The APK filename.
1300
1301 Returns:
1302 The parsed SDK version string.
1303
1304 Raises:
1305 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001306 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001307 proc = Run(
1308 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1309 stderr=subprocess.PIPE)
1310 stdoutdata, stderrdata = proc.communicate()
1311 if proc.returncode != 0:
1312 raise ExternalError(
1313 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1314 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001315
Tao Baof47bf0f2018-03-21 23:28:51 -07001316 for line in stdoutdata.split("\n"):
1317 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001318 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1319 if m:
1320 return m.group(1)
1321 raise ExternalError("No minSdkVersion returned by aapt")
1322
1323
1324def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001325 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001326
Tao Baof47bf0f2018-03-21 23:28:51 -07001327 If minSdkVersion is set to a codename, it is translated to a number using the
1328 provided map.
1329
1330 Args:
1331 apk_name: The APK filename.
1332
1333 Returns:
1334 The parsed SDK version number.
1335
1336 Raises:
1337 ExternalError: On failing to get the min SDK version number.
1338 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001339 version = GetMinSdkVersion(apk_name)
1340 try:
1341 return int(version)
1342 except ValueError:
1343 # Not a decimal number. Codename?
1344 if version in codename_to_api_level_map:
1345 return codename_to_api_level_map[version]
1346 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001347 raise ExternalError(
1348 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1349 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001350
1351
1352def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001353 codename_to_api_level_map=None, whole_file=False,
1354 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001355 """Sign the input_name zip/jar/apk, producing output_name. Use the
1356 given key and password (the latter may be None if the key does not
1357 have a password.
1358
Doug Zongker951495f2009-08-14 12:44:19 -07001359 If whole_file is true, use the "-w" option to SignApk to embed a
1360 signature that covers the whole file in the archive comment of the
1361 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001362
1363 min_api_level is the API Level (int) of the oldest platform this file may end
1364 up on. If not specified for an APK, the API Level is obtained by interpreting
1365 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1366
1367 codename_to_api_level_map is needed to translate the codename which may be
1368 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001369
1370 Caller may optionally specify extra args to be passed to SignApk, which
1371 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001372 """
Tao Bao76def242017-11-21 09:25:31 -08001373 if codename_to_api_level_map is None:
1374 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001375 if extra_signapk_args is None:
1376 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001377
Alex Klyubin9667b182015-12-10 13:38:50 -08001378 java_library_path = os.path.join(
1379 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1380
Tao Baoe95540e2016-11-08 12:08:53 -08001381 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1382 ["-Djava.library.path=" + java_library_path,
1383 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001384 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001385 if whole_file:
1386 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001387
1388 min_sdk_version = min_api_level
1389 if min_sdk_version is None:
1390 if not whole_file:
1391 min_sdk_version = GetMinSdkVersionInt(
1392 input_name, codename_to_api_level_map)
1393 if min_sdk_version is not None:
1394 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1395
T.R. Fullhart37e10522013-03-18 10:31:26 -07001396 cmd.extend([key + OPTIONS.public_key_suffix,
1397 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001398 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001399
Tao Bao73dd4f42018-10-04 16:25:33 -07001400 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001401 if password is not None:
1402 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001403 stdoutdata, _ = proc.communicate(password)
1404 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001405 raise ExternalError(
1406 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001407 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001408
Doug Zongkereef39442009-04-02 12:14:19 -07001409
Doug Zongker37974732010-09-16 17:44:38 -07001410def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001411 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001412
Tao Bao9dd909e2017-11-14 11:27:32 -08001413 For non-AVB images, raise exception if the data is too big. Print a warning
1414 if the data is nearing the maximum size.
1415
1416 For AVB images, the actual image size should be identical to the limit.
1417
1418 Args:
1419 data: A string that contains all the data for the partition.
1420 target: The partition name. The ".img" suffix is optional.
1421 info_dict: The dict to be looked up for relevant info.
1422 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001423 if target.endswith(".img"):
1424 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001425 mount_point = "/" + target
1426
Ying Wangf8824af2014-06-03 14:07:27 -07001427 fs_type = None
1428 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001429 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001430 if mount_point == "/userdata":
1431 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001432 p = info_dict["fstab"][mount_point]
1433 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001434 device = p.device
1435 if "/" in device:
1436 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001437 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001438 if not fs_type or not limit:
1439 return
Doug Zongkereef39442009-04-02 12:14:19 -07001440
Andrew Boie0f9aec82012-02-14 09:32:52 -08001441 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001442 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1443 # path.
1444 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1445 if size != limit:
1446 raise ExternalError(
1447 "Mismatching image size for %s: expected %d actual %d" % (
1448 target, limit, size))
1449 else:
1450 pct = float(size) * 100.0 / limit
1451 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1452 if pct >= 99.0:
1453 raise ExternalError(msg)
1454 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001455 logger.warning("\n WARNING: %s\n", msg)
1456 else:
1457 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001458
1459
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001460def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001461 """Parses the APK certs info from a given target-files zip.
1462
1463 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1464 tuple with the following elements: (1) a dictionary that maps packages to
1465 certs (based on the "certificate" and "private_key" attributes in the file;
1466 (2) a string representing the extension of compressed APKs in the target files
1467 (e.g ".gz", ".bro").
1468
1469 Args:
1470 tf_zip: The input target_files ZipFile (already open).
1471
1472 Returns:
1473 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1474 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1475 no compressed APKs.
1476 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001477 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001478 compressed_extension = None
1479
Tao Bao0f990332017-09-08 19:02:54 -07001480 # META/apkcerts.txt contains the info for _all_ the packages known at build
1481 # time. Filter out the ones that are not installed.
1482 installed_files = set()
1483 for name in tf_zip.namelist():
1484 basename = os.path.basename(name)
1485 if basename:
1486 installed_files.add(basename)
1487
Tao Baoda30cfa2017-12-01 16:19:46 -08001488 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001489 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001490 if not line:
1491 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001492 m = re.match(
1493 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1494 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1495 line)
1496 if not m:
1497 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001498
Tao Bao818ddf52018-01-05 11:17:34 -08001499 matches = m.groupdict()
1500 cert = matches["CERT"]
1501 privkey = matches["PRIVKEY"]
1502 name = matches["NAME"]
1503 this_compressed_extension = matches["COMPRESSED"]
1504
1505 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1506 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1507 if cert in SPECIAL_CERT_STRINGS and not privkey:
1508 certmap[name] = cert
1509 elif (cert.endswith(OPTIONS.public_key_suffix) and
1510 privkey.endswith(OPTIONS.private_key_suffix) and
1511 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1512 certmap[name] = cert[:-public_key_suffix_len]
1513 else:
1514 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1515
1516 if not this_compressed_extension:
1517 continue
1518
1519 # Only count the installed files.
1520 filename = name + '.' + this_compressed_extension
1521 if filename not in installed_files:
1522 continue
1523
1524 # Make sure that all the values in the compression map have the same
1525 # extension. We don't support multiple compression methods in the same
1526 # system image.
1527 if compressed_extension:
1528 if this_compressed_extension != compressed_extension:
1529 raise ValueError(
1530 "Multiple compressed extensions: {} vs {}".format(
1531 compressed_extension, this_compressed_extension))
1532 else:
1533 compressed_extension = this_compressed_extension
1534
1535 return (certmap,
1536 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001537
1538
Doug Zongkereef39442009-04-02 12:14:19 -07001539COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001540Global options
1541
1542 -p (--path) <dir>
1543 Prepend <dir>/bin to the list of places to search for binaries run by this
1544 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001545
Doug Zongker05d3dea2009-06-22 11:32:31 -07001546 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001547 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001548
Tao Bao30df8b42018-04-23 15:32:53 -07001549 -x (--extra) <key=value>
1550 Add a key/value pair to the 'extras' dict, which device-specific extension
1551 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001552
Doug Zongkereef39442009-04-02 12:14:19 -07001553 -v (--verbose)
1554 Show command lines being executed.
1555
1556 -h (--help)
1557 Display this usage message and exit.
1558"""
1559
1560def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001561 print(docstring.rstrip("\n"))
1562 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001563
1564
1565def ParseOptions(argv,
1566 docstring,
1567 extra_opts="", extra_long_opts=(),
1568 extra_option_handler=None):
1569 """Parse the options in argv and return any arguments that aren't
1570 flags. docstring is the calling module's docstring, to be displayed
1571 for errors and -h. extra_opts and extra_long_opts are for flags
1572 defined by the caller, which are processed by passing them to
1573 extra_option_handler."""
1574
1575 try:
1576 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001577 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001578 ["help", "verbose", "path=", "signapk_path=",
1579 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001580 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001581 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1582 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001583 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001584 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001585 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001586 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001587 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001588 sys.exit(2)
1589
Doug Zongkereef39442009-04-02 12:14:19 -07001590 for o, a in opts:
1591 if o in ("-h", "--help"):
1592 Usage(docstring)
1593 sys.exit()
1594 elif o in ("-v", "--verbose"):
1595 OPTIONS.verbose = True
1596 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001597 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001598 elif o in ("--signapk_path",):
1599 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001600 elif o in ("--signapk_shared_library_path",):
1601 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001602 elif o in ("--extra_signapk_args",):
1603 OPTIONS.extra_signapk_args = shlex.split(a)
1604 elif o in ("--java_path",):
1605 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001606 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001607 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001608 elif o in ("--public_key_suffix",):
1609 OPTIONS.public_key_suffix = a
1610 elif o in ("--private_key_suffix",):
1611 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001612 elif o in ("--boot_signer_path",):
1613 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001614 elif o in ("--boot_signer_args",):
1615 OPTIONS.boot_signer_args = shlex.split(a)
1616 elif o in ("--verity_signer_path",):
1617 OPTIONS.verity_signer_path = a
1618 elif o in ("--verity_signer_args",):
1619 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001620 elif o in ("-s", "--device_specific"):
1621 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001622 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001623 key, value = a.split("=", 1)
1624 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001625 else:
1626 if extra_option_handler is None or not extra_option_handler(o, a):
1627 assert False, "unknown option \"%s\"" % (o,)
1628
Doug Zongker85448772014-09-09 14:59:20 -07001629 if OPTIONS.search_path:
1630 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1631 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001632
1633 return args
1634
1635
Tao Bao4c851b12016-09-19 13:54:38 -07001636def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001637 """Make a temp file and add it to the list of things to be deleted
1638 when Cleanup() is called. Return the filename."""
1639 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1640 os.close(fd)
1641 OPTIONS.tempfiles.append(fn)
1642 return fn
1643
1644
Tao Bao1c830bf2017-12-25 10:43:47 -08001645def MakeTempDir(prefix='tmp', suffix=''):
1646 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1647
1648 Returns:
1649 The absolute pathname of the new directory.
1650 """
1651 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1652 OPTIONS.tempfiles.append(dir_name)
1653 return dir_name
1654
1655
Doug Zongkereef39442009-04-02 12:14:19 -07001656def Cleanup():
1657 for i in OPTIONS.tempfiles:
1658 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001659 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001660 else:
1661 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001662 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001663
1664
1665class PasswordManager(object):
1666 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001667 self.editor = os.getenv("EDITOR")
1668 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001669
1670 def GetPasswords(self, items):
1671 """Get passwords corresponding to each string in 'items',
1672 returning a dict. (The dict may have keys in addition to the
1673 values in 'items'.)
1674
1675 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1676 user edit that file to add more needed passwords. If no editor is
1677 available, or $ANDROID_PW_FILE isn't define, prompts the user
1678 interactively in the ordinary way.
1679 """
1680
1681 current = self.ReadFile()
1682
1683 first = True
1684 while True:
1685 missing = []
1686 for i in items:
1687 if i not in current or not current[i]:
1688 missing.append(i)
1689 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001690 if not missing:
1691 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001692
1693 for i in missing:
1694 current[i] = ""
1695
1696 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001697 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001698 if sys.version_info[0] >= 3:
1699 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001700 answer = raw_input("try to edit again? [y]> ").strip()
1701 if answer and answer[0] not in 'yY':
1702 raise RuntimeError("key passwords unavailable")
1703 first = False
1704
1705 current = self.UpdateAndReadFile(current)
1706
Dan Albert8b72aef2015-03-23 19:13:21 -07001707 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001708 """Prompt the user to enter a value (password) for each key in
1709 'current' whose value is fales. Returns a new dict with all the
1710 values.
1711 """
1712 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001713 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001714 if v:
1715 result[k] = v
1716 else:
1717 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001718 result[k] = getpass.getpass(
1719 "Enter password for %s key> " % k).strip()
1720 if result[k]:
1721 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001722 return result
1723
1724 def UpdateAndReadFile(self, current):
1725 if not self.editor or not self.pwfile:
1726 return self.PromptResult(current)
1727
1728 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001729 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001730 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1731 f.write("# (Additional spaces are harmless.)\n\n")
1732
1733 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001734 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001735 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001736 f.write("[[[ %s ]]] %s\n" % (v, k))
1737 if not v and first_line is None:
1738 # position cursor on first line with no password.
1739 first_line = i + 4
1740 f.close()
1741
Tao Bao986ee862018-10-04 15:46:16 -07001742 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001743
1744 return self.ReadFile()
1745
1746 def ReadFile(self):
1747 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001748 if self.pwfile is None:
1749 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001750 try:
1751 f = open(self.pwfile, "r")
1752 for line in f:
1753 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001754 if not line or line[0] == '#':
1755 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001756 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1757 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001758 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001759 else:
1760 result[m.group(2)] = m.group(1)
1761 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001762 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001763 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001764 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001765 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001766
1767
Dan Albert8e0178d2015-01-27 15:53:15 -08001768def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1769 compress_type=None):
1770 import datetime
1771
1772 # http://b/18015246
1773 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1774 # for files larger than 2GiB. We can work around this by adjusting their
1775 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1776 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1777 # it isn't clear to me exactly what circumstances cause this).
1778 # `zipfile.write()` must be used directly to work around this.
1779 #
1780 # This mess can be avoided if we port to python3.
1781 saved_zip64_limit = zipfile.ZIP64_LIMIT
1782 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1783
1784 if compress_type is None:
1785 compress_type = zip_file.compression
1786 if arcname is None:
1787 arcname = filename
1788
1789 saved_stat = os.stat(filename)
1790
1791 try:
1792 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1793 # file to be zipped and reset it when we're done.
1794 os.chmod(filename, perms)
1795
1796 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001797 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1798 # intentional. zip stores datetimes in local time without a time zone
1799 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1800 # in the zip archive.
1801 local_epoch = datetime.datetime.fromtimestamp(0)
1802 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001803 os.utime(filename, (timestamp, timestamp))
1804
1805 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1806 finally:
1807 os.chmod(filename, saved_stat.st_mode)
1808 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1809 zipfile.ZIP64_LIMIT = saved_zip64_limit
1810
1811
Tao Bao58c1b962015-05-20 09:32:18 -07001812def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001813 compress_type=None):
1814 """Wrap zipfile.writestr() function to work around the zip64 limit.
1815
1816 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1817 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1818 when calling crc32(bytes).
1819
1820 But it still works fine to write a shorter string into a large zip file.
1821 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1822 when we know the string won't be too long.
1823 """
1824
1825 saved_zip64_limit = zipfile.ZIP64_LIMIT
1826 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1827
1828 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1829 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001830 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001831 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001832 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001833 else:
Tao Baof3282b42015-04-01 11:21:55 -07001834 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001835 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1836 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1837 # such a case (since
1838 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1839 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1840 # permission bits. We follow the logic in Python 3 to get consistent
1841 # behavior between using the two versions.
1842 if not zinfo.external_attr:
1843 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001844
1845 # If compress_type is given, it overrides the value in zinfo.
1846 if compress_type is not None:
1847 zinfo.compress_type = compress_type
1848
Tao Bao58c1b962015-05-20 09:32:18 -07001849 # If perms is given, it has a priority.
1850 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001851 # If perms doesn't set the file type, mark it as a regular file.
1852 if perms & 0o770000 == 0:
1853 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001854 zinfo.external_attr = perms << 16
1855
Tao Baof3282b42015-04-01 11:21:55 -07001856 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001857 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1858
Dan Albert8b72aef2015-03-23 19:13:21 -07001859 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001860 zipfile.ZIP64_LIMIT = saved_zip64_limit
1861
1862
Tao Bao89d7ab22017-12-14 17:05:33 -08001863def ZipDelete(zip_filename, entries):
1864 """Deletes entries from a ZIP file.
1865
1866 Since deleting entries from a ZIP file is not supported, it shells out to
1867 'zip -d'.
1868
1869 Args:
1870 zip_filename: The name of the ZIP file.
1871 entries: The name of the entry, or the list of names to be deleted.
1872
1873 Raises:
1874 AssertionError: In case of non-zero return from 'zip'.
1875 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001876 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001877 entries = [entries]
1878 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001879 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001880
1881
Tao Baof3282b42015-04-01 11:21:55 -07001882def ZipClose(zip_file):
1883 # http://b/18015246
1884 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1885 # central directory.
1886 saved_zip64_limit = zipfile.ZIP64_LIMIT
1887 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1888
1889 zip_file.close()
1890
1891 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001892
1893
1894class DeviceSpecificParams(object):
1895 module = None
1896 def __init__(self, **kwargs):
1897 """Keyword arguments to the constructor become attributes of this
1898 object, which is passed to all functions in the device-specific
1899 module."""
Tao Bao38884282019-07-10 22:20:56 -07001900 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07001901 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001902 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001903
1904 if self.module is None:
1905 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001906 if not path:
1907 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001908 try:
1909 if os.path.isdir(path):
1910 info = imp.find_module("releasetools", [path])
1911 else:
1912 d, f = os.path.split(path)
1913 b, x = os.path.splitext(f)
1914 if x == ".py":
1915 f = b
1916 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001917 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001918 self.module = imp.load_module("device_specific", *info)
1919 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001920 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001921
1922 def _DoCall(self, function_name, *args, **kwargs):
1923 """Call the named function in the device-specific module, passing
1924 the given args and kwargs. The first argument to the call will be
1925 the DeviceSpecific object itself. If there is no module, or the
1926 module does not define the function, return the value of the
1927 'default' kwarg (which itself defaults to None)."""
1928 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001929 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001930 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1931
1932 def FullOTA_Assertions(self):
1933 """Called after emitting the block of assertions at the top of a
1934 full OTA package. Implementations can add whatever additional
1935 assertions they like."""
1936 return self._DoCall("FullOTA_Assertions")
1937
Doug Zongkere5ff5902012-01-17 10:55:37 -08001938 def FullOTA_InstallBegin(self):
1939 """Called at the start of full OTA installation."""
1940 return self._DoCall("FullOTA_InstallBegin")
1941
Yifan Hong10c530d2018-12-27 17:34:18 -08001942 def FullOTA_GetBlockDifferences(self):
1943 """Called during full OTA installation and verification.
1944 Implementation should return a list of BlockDifference objects describing
1945 the update on each additional partitions.
1946 """
1947 return self._DoCall("FullOTA_GetBlockDifferences")
1948
Doug Zongker05d3dea2009-06-22 11:32:31 -07001949 def FullOTA_InstallEnd(self):
1950 """Called at the end of full OTA installation; typically this is
1951 used to install the image for the device's baseband processor."""
1952 return self._DoCall("FullOTA_InstallEnd")
1953
1954 def IncrementalOTA_Assertions(self):
1955 """Called after emitting the block of assertions at the top of an
1956 incremental OTA package. Implementations can add whatever
1957 additional assertions they like."""
1958 return self._DoCall("IncrementalOTA_Assertions")
1959
Doug Zongkere5ff5902012-01-17 10:55:37 -08001960 def IncrementalOTA_VerifyBegin(self):
1961 """Called at the start of the verification phase of incremental
1962 OTA installation; additional checks can be placed here to abort
1963 the script before any changes are made."""
1964 return self._DoCall("IncrementalOTA_VerifyBegin")
1965
Doug Zongker05d3dea2009-06-22 11:32:31 -07001966 def IncrementalOTA_VerifyEnd(self):
1967 """Called at the end of the verification phase of incremental OTA
1968 installation; additional checks can be placed here to abort the
1969 script before any changes are made."""
1970 return self._DoCall("IncrementalOTA_VerifyEnd")
1971
Doug Zongkere5ff5902012-01-17 10:55:37 -08001972 def IncrementalOTA_InstallBegin(self):
1973 """Called at the start of incremental OTA installation (after
1974 verification is complete)."""
1975 return self._DoCall("IncrementalOTA_InstallBegin")
1976
Yifan Hong10c530d2018-12-27 17:34:18 -08001977 def IncrementalOTA_GetBlockDifferences(self):
1978 """Called during incremental OTA installation and verification.
1979 Implementation should return a list of BlockDifference objects describing
1980 the update on each additional partitions.
1981 """
1982 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1983
Doug Zongker05d3dea2009-06-22 11:32:31 -07001984 def IncrementalOTA_InstallEnd(self):
1985 """Called at the end of incremental OTA installation; typically
1986 this is used to install the image for the device's baseband
1987 processor."""
1988 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001989
Tao Bao9bc6bb22015-11-09 16:58:28 -08001990 def VerifyOTA_Assertions(self):
1991 return self._DoCall("VerifyOTA_Assertions")
1992
Tao Bao76def242017-11-21 09:25:31 -08001993
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001994class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001995 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001996 self.name = name
1997 self.data = data
1998 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001999 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002000 self.sha1 = sha1(data).hexdigest()
2001
2002 @classmethod
2003 def FromLocalFile(cls, name, diskname):
2004 f = open(diskname, "rb")
2005 data = f.read()
2006 f.close()
2007 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002008
2009 def WriteToTemp(self):
2010 t = tempfile.NamedTemporaryFile()
2011 t.write(self.data)
2012 t.flush()
2013 return t
2014
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002015 def WriteToDir(self, d):
2016 with open(os.path.join(d, self.name), "wb") as fp:
2017 fp.write(self.data)
2018
Geremy Condra36bd3652014-02-06 19:45:10 -08002019 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002020 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002021
Tao Bao76def242017-11-21 09:25:31 -08002022
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002023DIFF_PROGRAM_BY_EXT = {
2024 ".gz" : "imgdiff",
2025 ".zip" : ["imgdiff", "-z"],
2026 ".jar" : ["imgdiff", "-z"],
2027 ".apk" : ["imgdiff", "-z"],
2028 ".img" : "imgdiff",
2029 }
2030
Tao Bao76def242017-11-21 09:25:31 -08002031
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002032class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002033 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002034 self.tf = tf
2035 self.sf = sf
2036 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002037 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002038
2039 def ComputePatch(self):
2040 """Compute the patch (as a string of data) needed to turn sf into
2041 tf. Returns the same tuple as GetPatch()."""
2042
2043 tf = self.tf
2044 sf = self.sf
2045
Doug Zongker24cd2802012-08-14 16:36:15 -07002046 if self.diff_program:
2047 diff_program = self.diff_program
2048 else:
2049 ext = os.path.splitext(tf.name)[1]
2050 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002051
2052 ttemp = tf.WriteToTemp()
2053 stemp = sf.WriteToTemp()
2054
2055 ext = os.path.splitext(tf.name)[1]
2056
2057 try:
2058 ptemp = tempfile.NamedTemporaryFile()
2059 if isinstance(diff_program, list):
2060 cmd = copy.copy(diff_program)
2061 else:
2062 cmd = [diff_program]
2063 cmd.append(stemp.name)
2064 cmd.append(ttemp.name)
2065 cmd.append(ptemp.name)
2066 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002067 err = []
2068 def run():
2069 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002070 if e:
2071 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002072 th = threading.Thread(target=run)
2073 th.start()
2074 th.join(timeout=300) # 5 mins
2075 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002076 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002077 p.terminate()
2078 th.join(5)
2079 if th.is_alive():
2080 p.kill()
2081 th.join()
2082
Tianjie Xua2a9f992018-01-05 15:15:54 -08002083 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002084 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002085 self.patch = None
2086 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002087 diff = ptemp.read()
2088 finally:
2089 ptemp.close()
2090 stemp.close()
2091 ttemp.close()
2092
2093 self.patch = diff
2094 return self.tf, self.sf, self.patch
2095
2096
2097 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002098 """Returns a tuple of (target_file, source_file, patch_data).
2099
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002100 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002101 computing the patch failed.
2102 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002103 return self.tf, self.sf, self.patch
2104
2105
2106def ComputeDifferences(diffs):
2107 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002108 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002109
2110 # Do the largest files first, to try and reduce the long-pole effect.
2111 by_size = [(i.tf.size, i) for i in diffs]
2112 by_size.sort(reverse=True)
2113 by_size = [i[1] for i in by_size]
2114
2115 lock = threading.Lock()
2116 diff_iter = iter(by_size) # accessed under lock
2117
2118 def worker():
2119 try:
2120 lock.acquire()
2121 for d in diff_iter:
2122 lock.release()
2123 start = time.time()
2124 d.ComputePatch()
2125 dur = time.time() - start
2126 lock.acquire()
2127
2128 tf, sf, patch = d.GetPatch()
2129 if sf.name == tf.name:
2130 name = tf.name
2131 else:
2132 name = "%s (%s)" % (tf.name, sf.name)
2133 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002134 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002135 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002136 logger.info(
2137 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2138 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002139 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002140 except Exception:
2141 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002142 raise
2143
2144 # start worker threads; wait for them all to finish.
2145 threads = [threading.Thread(target=worker)
2146 for i in range(OPTIONS.worker_threads)]
2147 for th in threads:
2148 th.start()
2149 while threads:
2150 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002151
2152
Dan Albert8b72aef2015-03-23 19:13:21 -07002153class BlockDifference(object):
2154 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002155 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002156 self.tgt = tgt
2157 self.src = src
2158 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002159 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002160 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002161
Tao Baodd2a5892015-03-12 12:32:37 -07002162 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002163 version = max(
2164 int(i) for i in
2165 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002166 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002167 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002168
Tianjie Xu41976c72019-07-03 13:57:01 -07002169 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2170 version=self.version,
2171 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002172 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002173 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002174 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002175 self.touched_src_ranges = b.touched_src_ranges
2176 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002177
Yifan Hong10c530d2018-12-27 17:34:18 -08002178 # On devices with dynamic partitions, for new partitions,
2179 # src is None but OPTIONS.source_info_dict is not.
2180 if OPTIONS.source_info_dict is None:
2181 is_dynamic_build = OPTIONS.info_dict.get(
2182 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002183 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002184 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002185 is_dynamic_build = OPTIONS.source_info_dict.get(
2186 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002187 is_dynamic_source = partition in shlex.split(
2188 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002189
Yifan Hongbb2658d2019-01-25 12:30:58 -08002190 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002191 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2192
Yifan Hongbb2658d2019-01-25 12:30:58 -08002193 # For dynamic partitions builds, check partition list in both source
2194 # and target build because new partitions may be added, and existing
2195 # partitions may be removed.
2196 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2197
Yifan Hong10c530d2018-12-27 17:34:18 -08002198 if is_dynamic:
2199 self.device = 'map_partition("%s")' % partition
2200 else:
2201 if OPTIONS.source_info_dict is None:
2202 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2203 else:
2204 _, device_path = GetTypeAndDevice("/" + partition,
2205 OPTIONS.source_info_dict)
2206 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002207
Tao Baod8d14be2016-02-04 14:26:02 -08002208 @property
2209 def required_cache(self):
2210 return self._required_cache
2211
Tao Bao76def242017-11-21 09:25:31 -08002212 def WriteScript(self, script, output_zip, progress=None,
2213 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002214 if not self.src:
2215 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002216 script.Print("Patching %s image unconditionally..." % (self.partition,))
2217 else:
2218 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002219
Dan Albert8b72aef2015-03-23 19:13:21 -07002220 if progress:
2221 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002222 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002223
2224 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002225 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002226
Tao Bao9bc6bb22015-11-09 16:58:28 -08002227 def WriteStrictVerifyScript(self, script):
2228 """Verify all the blocks in the care_map, including clobbered blocks.
2229
2230 This differs from the WriteVerifyScript() function: a) it prints different
2231 error messages; b) it doesn't allow half-way updated images to pass the
2232 verification."""
2233
2234 partition = self.partition
2235 script.Print("Verifying %s..." % (partition,))
2236 ranges = self.tgt.care_map
2237 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002238 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002239 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2240 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002241 self.device, ranges_str,
2242 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002243 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002244 script.AppendExtra("")
2245
Tao Baod522bdc2016-04-12 15:53:16 -07002246 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002247 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002248
2249 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002250 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002251 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002252
2253 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002254 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002255 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002256 ranges = self.touched_src_ranges
2257 expected_sha1 = self.touched_src_sha1
2258 else:
2259 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2260 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002261
2262 # No blocks to be checked, skipping.
2263 if not ranges:
2264 return
2265
Tao Bao5ece99d2015-05-12 11:42:31 -07002266 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002267 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002268 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002269 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2270 '"%s.patch.dat")) then' % (
2271 self.device, ranges_str, expected_sha1,
2272 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002273 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002274 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002275
Tianjie Xufc3422a2015-12-15 11:53:59 -08002276 if self.version >= 4:
2277
2278 # Bug: 21124327
2279 # When generating incrementals for the system and vendor partitions in
2280 # version 4 or newer, explicitly check the first block (which contains
2281 # the superblock) of the partition to see if it's what we expect. If
2282 # this check fails, give an explicit log message about the partition
2283 # having been remounted R/W (the most likely explanation).
2284 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002285 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002286
2287 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002288 if partition == "system":
2289 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2290 else:
2291 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002292 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002293 'ifelse (block_image_recover({device}, "{ranges}") && '
2294 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002295 'package_extract_file("{partition}.transfer.list"), '
2296 '"{partition}.new.dat", "{partition}.patch.dat"), '
2297 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002298 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002299 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002300 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002301
Tao Baodd2a5892015-03-12 12:32:37 -07002302 # Abort the OTA update. Note that the incremental OTA cannot be applied
2303 # even if it may match the checksum of the target partition.
2304 # a) If version < 3, operations like move and erase will make changes
2305 # unconditionally and damage the partition.
2306 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002307 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002308 if partition == "system":
2309 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2310 else:
2311 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2312 script.AppendExtra((
2313 'abort("E%d: %s partition has unexpected contents");\n'
2314 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002315
Yifan Hong10c530d2018-12-27 17:34:18 -08002316 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002317 partition = self.partition
2318 script.Print('Verifying the updated %s image...' % (partition,))
2319 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2320 ranges = self.tgt.care_map
2321 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002322 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002323 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002324 self.device, ranges_str,
2325 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002326
2327 # Bug: 20881595
2328 # Verify that extended blocks are really zeroed out.
2329 if self.tgt.extended:
2330 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002331 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002332 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002333 self.device, ranges_str,
2334 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002335 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002336 if partition == "system":
2337 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2338 else:
2339 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002340 script.AppendExtra(
2341 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002342 ' abort("E%d: %s partition has unexpected non-zero contents after '
2343 'OTA update");\n'
2344 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002345 else:
2346 script.Print('Verified the updated %s image.' % (partition,))
2347
Tianjie Xu209db462016-05-24 17:34:52 -07002348 if partition == "system":
2349 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2350 else:
2351 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2352
Tao Bao5fcaaef2015-06-01 13:40:49 -07002353 script.AppendExtra(
2354 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002355 ' abort("E%d: %s partition has unexpected contents after OTA '
2356 'update");\n'
2357 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002358
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002359 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002360 ZipWrite(output_zip,
2361 '{}.transfer.list'.format(self.path),
2362 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002363
Tao Bao76def242017-11-21 09:25:31 -08002364 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2365 # its size. Quailty 9 almost triples the compression time but doesn't
2366 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002367 # zip | brotli(quality 6) | brotli(quality 9)
2368 # compressed_size: 942M | 869M (~8% reduced) | 854M
2369 # compression_time: 75s | 265s | 719s
2370 # decompression_time: 15s | 25s | 25s
2371
2372 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002373 brotli_cmd = ['brotli', '--quality=6',
2374 '--output={}.new.dat.br'.format(self.path),
2375 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002376 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002377 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002378
2379 new_data_name = '{}.new.dat.br'.format(self.partition)
2380 ZipWrite(output_zip,
2381 '{}.new.dat.br'.format(self.path),
2382 new_data_name,
2383 compress_type=zipfile.ZIP_STORED)
2384 else:
2385 new_data_name = '{}.new.dat'.format(self.partition)
2386 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2387
Dan Albert8e0178d2015-01-27 15:53:15 -08002388 ZipWrite(output_zip,
2389 '{}.patch.dat'.format(self.path),
2390 '{}.patch.dat'.format(self.partition),
2391 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002392
Tianjie Xu209db462016-05-24 17:34:52 -07002393 if self.partition == "system":
2394 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2395 else:
2396 code = ErrorCode.VENDOR_UPDATE_FAILURE
2397
Yifan Hong10c530d2018-12-27 17:34:18 -08002398 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002399 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002400 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002401 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002402 device=self.device, partition=self.partition,
2403 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002404 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002405
Dan Albert8b72aef2015-03-23 19:13:21 -07002406 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002407 data = source.ReadRangeSet(ranges)
2408 ctx = sha1()
2409
2410 for p in data:
2411 ctx.update(p)
2412
2413 return ctx.hexdigest()
2414
Tao Baoe9b61912015-07-09 17:37:49 -07002415 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2416 """Return the hash value for all zero blocks."""
2417 zero_block = '\x00' * 4096
2418 ctx = sha1()
2419 for _ in range(num_blocks):
2420 ctx.update(zero_block)
2421
2422 return ctx.hexdigest()
2423
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002424
Tianjie Xu41976c72019-07-03 13:57:01 -07002425# Expose these two classes to support vendor-specific scripts
2426DataImage = images.DataImage
2427EmptyImage = images.EmptyImage
2428
Tao Bao76def242017-11-21 09:25:31 -08002429
Doug Zongker96a57e72010-09-26 14:57:41 -07002430# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002431PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002432 "ext4": "EMMC",
2433 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002434 "f2fs": "EMMC",
2435 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002436}
Doug Zongker96a57e72010-09-26 14:57:41 -07002437
Tao Bao76def242017-11-21 09:25:31 -08002438
Doug Zongker96a57e72010-09-26 14:57:41 -07002439def GetTypeAndDevice(mount_point, info):
2440 fstab = info["fstab"]
2441 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002442 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2443 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002444 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002445 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002446
2447
2448def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002449 """Parses and converts a PEM-encoded certificate into DER-encoded.
2450
2451 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2452
2453 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002454 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002455 """
2456 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002457 save = False
2458 for line in data.split("\n"):
2459 if "--END CERTIFICATE--" in line:
2460 break
2461 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002462 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002463 if "--BEGIN CERTIFICATE--" in line:
2464 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002465 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002466 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002467
Tao Bao04e1f012018-02-04 12:13:35 -08002468
2469def ExtractPublicKey(cert):
2470 """Extracts the public key (PEM-encoded) from the given certificate file.
2471
2472 Args:
2473 cert: The certificate filename.
2474
2475 Returns:
2476 The public key string.
2477
2478 Raises:
2479 AssertionError: On non-zero return from 'openssl'.
2480 """
2481 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2482 # While openssl 1.1 writes the key into the given filename followed by '-out',
2483 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2484 # stdout instead.
2485 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2486 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2487 pubkey, stderrdata = proc.communicate()
2488 assert proc.returncode == 0, \
2489 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2490 return pubkey
2491
2492
Tao Bao1ac886e2019-06-26 11:58:22 -07002493def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002494 """Extracts the AVB public key from the given public or private key.
2495
2496 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002497 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002498 key: The input key file, which should be PEM-encoded public or private key.
2499
2500 Returns:
2501 The path to the extracted AVB public key file.
2502 """
2503 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2504 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002505 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002506 return output
2507
2508
Doug Zongker412c02f2014-02-13 10:58:24 -08002509def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2510 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002511 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002512
Tao Bao6d5d6232018-03-09 17:04:42 -08002513 Most of the space in the boot and recovery images is just the kernel, which is
2514 identical for the two, so the resulting patch should be efficient. Add it to
2515 the output zip, along with a shell script that is run from init.rc on first
2516 boot to actually do the patching and install the new recovery image.
2517
2518 Args:
2519 input_dir: The top-level input directory of the target-files.zip.
2520 output_sink: The callback function that writes the result.
2521 recovery_img: File object for the recovery image.
2522 boot_img: File objects for the boot image.
2523 info_dict: A dict returned by common.LoadInfoDict() on the input
2524 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002525 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002526 if info_dict is None:
2527 info_dict = OPTIONS.info_dict
2528
Tao Bao6d5d6232018-03-09 17:04:42 -08002529 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002530
Tao Baof2cffbd2015-07-22 12:33:18 -07002531 if full_recovery_image:
2532 output_sink("etc/recovery.img", recovery_img.data)
2533
2534 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002535 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002536 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002537 # With system-root-image, boot and recovery images will have mismatching
2538 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2539 # to handle such a case.
2540 if system_root_image:
2541 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002542 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002543 assert not os.path.exists(path)
2544 else:
2545 diff_program = ["imgdiff"]
2546 if os.path.exists(path):
2547 diff_program.append("-b")
2548 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002549 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002550 else:
2551 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002552
2553 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2554 _, _, patch = d.ComputePatch()
2555 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002556
Dan Albertebb19aa2015-03-27 19:11:53 -07002557 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002558 # The following GetTypeAndDevice()s need to use the path in the target
2559 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002560 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2561 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2562 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002563 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002564
Tao Baof2cffbd2015-07-22 12:33:18 -07002565 if full_recovery_image:
2566 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002567if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2568 applypatch \\
2569 --flash /system/etc/recovery.img \\
2570 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2571 log -t recovery "Installing new recovery image: succeeded" || \\
2572 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002573else
2574 log -t recovery "Recovery image already installed"
2575fi
2576""" % {'type': recovery_type,
2577 'device': recovery_device,
2578 'sha1': recovery_img.sha1,
2579 'size': recovery_img.size}
2580 else:
2581 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002582if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2583 applypatch %(bonus_args)s \\
2584 --patch /system/recovery-from-boot.p \\
2585 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2586 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2587 log -t recovery "Installing new recovery image: succeeded" || \\
2588 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002589else
2590 log -t recovery "Recovery image already installed"
2591fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002592""" % {'boot_size': boot_img.size,
2593 'boot_sha1': boot_img.sha1,
2594 'recovery_size': recovery_img.size,
2595 'recovery_sha1': recovery_img.sha1,
2596 'boot_type': boot_type,
2597 'boot_device': boot_device,
2598 'recovery_type': recovery_type,
2599 'recovery_device': recovery_device,
2600 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002601
2602 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002603 # in the L release.
2604 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002605
Tao Bao32fcdab2018-10-12 10:30:39 -07002606 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002607
Tao Baoda30cfa2017-12-01 16:19:46 -08002608 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002609
2610
2611class DynamicPartitionUpdate(object):
2612 def __init__(self, src_group=None, tgt_group=None, progress=None,
2613 block_difference=None):
2614 self.src_group = src_group
2615 self.tgt_group = tgt_group
2616 self.progress = progress
2617 self.block_difference = block_difference
2618
2619 @property
2620 def src_size(self):
2621 if not self.block_difference:
2622 return 0
2623 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2624
2625 @property
2626 def tgt_size(self):
2627 if not self.block_difference:
2628 return 0
2629 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2630
2631 @staticmethod
2632 def _GetSparseImageSize(img):
2633 if not img:
2634 return 0
2635 return img.blocksize * img.total_blocks
2636
2637
2638class DynamicGroupUpdate(object):
2639 def __init__(self, src_size=None, tgt_size=None):
2640 # None: group does not exist. 0: no size limits.
2641 self.src_size = src_size
2642 self.tgt_size = tgt_size
2643
2644
2645class DynamicPartitionsDifference(object):
2646 def __init__(self, info_dict, block_diffs, progress_dict=None,
2647 source_info_dict=None):
2648 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002649 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002650
2651 self._remove_all_before_apply = False
2652 if source_info_dict is None:
2653 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002654 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002655
Tao Baof1113e92019-06-18 12:10:14 -07002656 block_diff_dict = collections.OrderedDict(
2657 [(e.partition, e) for e in block_diffs])
2658
Yifan Hong10c530d2018-12-27 17:34:18 -08002659 assert len(block_diff_dict) == len(block_diffs), \
2660 "Duplicated BlockDifference object for {}".format(
2661 [partition for partition, count in
2662 collections.Counter(e.partition for e in block_diffs).items()
2663 if count > 1])
2664
Yifan Hong79997e52019-01-23 16:56:19 -08002665 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002666
2667 for p, block_diff in block_diff_dict.items():
2668 self._partition_updates[p] = DynamicPartitionUpdate()
2669 self._partition_updates[p].block_difference = block_diff
2670
2671 for p, progress in progress_dict.items():
2672 if p in self._partition_updates:
2673 self._partition_updates[p].progress = progress
2674
2675 tgt_groups = shlex.split(info_dict.get(
2676 "super_partition_groups", "").strip())
2677 src_groups = shlex.split(source_info_dict.get(
2678 "super_partition_groups", "").strip())
2679
2680 for g in tgt_groups:
2681 for p in shlex.split(info_dict.get(
2682 "super_%s_partition_list" % g, "").strip()):
2683 assert p in self._partition_updates, \
2684 "{} is in target super_{}_partition_list but no BlockDifference " \
2685 "object is provided.".format(p, g)
2686 self._partition_updates[p].tgt_group = g
2687
2688 for g in src_groups:
2689 for p in shlex.split(source_info_dict.get(
2690 "super_%s_partition_list" % g, "").strip()):
2691 assert p in self._partition_updates, \
2692 "{} is in source super_{}_partition_list but no BlockDifference " \
2693 "object is provided.".format(p, g)
2694 self._partition_updates[p].src_group = g
2695
Yifan Hong45433e42019-01-18 13:55:25 -08002696 target_dynamic_partitions = set(shlex.split(info_dict.get(
2697 "dynamic_partition_list", "").strip()))
2698 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2699 if u.tgt_size)
2700 assert block_diffs_with_target == target_dynamic_partitions, \
2701 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2702 list(target_dynamic_partitions), list(block_diffs_with_target))
2703
2704 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2705 "dynamic_partition_list", "").strip()))
2706 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2707 if u.src_size)
2708 assert block_diffs_with_source == source_dynamic_partitions, \
2709 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2710 list(source_dynamic_partitions), list(block_diffs_with_source))
2711
Yifan Hong10c530d2018-12-27 17:34:18 -08002712 if self._partition_updates:
2713 logger.info("Updating dynamic partitions %s",
2714 self._partition_updates.keys())
2715
Yifan Hong79997e52019-01-23 16:56:19 -08002716 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002717
2718 for g in tgt_groups:
2719 self._group_updates[g] = DynamicGroupUpdate()
2720 self._group_updates[g].tgt_size = int(info_dict.get(
2721 "super_%s_group_size" % g, "0").strip())
2722
2723 for g in src_groups:
2724 if g not in self._group_updates:
2725 self._group_updates[g] = DynamicGroupUpdate()
2726 self._group_updates[g].src_size = int(source_info_dict.get(
2727 "super_%s_group_size" % g, "0").strip())
2728
2729 self._Compute()
2730
2731 def WriteScript(self, script, output_zip, write_verify_script=False):
2732 script.Comment('--- Start patching dynamic partitions ---')
2733 for p, u in self._partition_updates.items():
2734 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2735 script.Comment('Patch partition %s' % p)
2736 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2737 write_verify_script=False)
2738
2739 op_list_path = MakeTempFile()
2740 with open(op_list_path, 'w') as f:
2741 for line in self._op_list:
2742 f.write('{}\n'.format(line))
2743
2744 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2745
2746 script.Comment('Update dynamic partition metadata')
2747 script.AppendExtra('assert(update_dynamic_partitions('
2748 'package_extract_file("dynamic_partitions_op_list")));')
2749
2750 if write_verify_script:
2751 for p, u in self._partition_updates.items():
2752 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2753 u.block_difference.WritePostInstallVerifyScript(script)
2754 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2755
2756 for p, u in self._partition_updates.items():
2757 if u.tgt_size and u.src_size <= u.tgt_size:
2758 script.Comment('Patch partition %s' % p)
2759 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2760 write_verify_script=write_verify_script)
2761 if write_verify_script:
2762 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2763
2764 script.Comment('--- End patching dynamic partitions ---')
2765
2766 def _Compute(self):
2767 self._op_list = list()
2768
2769 def append(line):
2770 self._op_list.append(line)
2771
2772 def comment(line):
2773 self._op_list.append("# %s" % line)
2774
2775 if self._remove_all_before_apply:
2776 comment('Remove all existing dynamic partitions and groups before '
2777 'applying full OTA')
2778 append('remove_all_groups')
2779
2780 for p, u in self._partition_updates.items():
2781 if u.src_group and not u.tgt_group:
2782 append('remove %s' % p)
2783
2784 for p, u in self._partition_updates.items():
2785 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2786 comment('Move partition %s from %s to default' % (p, u.src_group))
2787 append('move %s default' % p)
2788
2789 for p, u in self._partition_updates.items():
2790 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2791 comment('Shrink partition %s from %d to %d' %
2792 (p, u.src_size, u.tgt_size))
2793 append('resize %s %s' % (p, u.tgt_size))
2794
2795 for g, u in self._group_updates.items():
2796 if u.src_size is not None and u.tgt_size is None:
2797 append('remove_group %s' % g)
2798 if (u.src_size is not None and u.tgt_size is not None and
2799 u.src_size > u.tgt_size):
2800 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2801 append('resize_group %s %d' % (g, u.tgt_size))
2802
2803 for g, u in self._group_updates.items():
2804 if u.src_size is None and u.tgt_size is not None:
2805 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2806 append('add_group %s %d' % (g, u.tgt_size))
2807 if (u.src_size is not None and u.tgt_size is not None and
2808 u.src_size < u.tgt_size):
2809 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2810 append('resize_group %s %d' % (g, u.tgt_size))
2811
2812 for p, u in self._partition_updates.items():
2813 if u.tgt_group and not u.src_group:
2814 comment('Add partition %s to group %s' % (p, u.tgt_group))
2815 append('add %s %s' % (p, u.tgt_group))
2816
2817 for p, u in self._partition_updates.items():
2818 if u.tgt_size and u.src_size < u.tgt_size:
2819 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2820 append('resize %s %d' % (p, u.tgt_size))
2821
2822 for p, u in self._partition_updates.items():
2823 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2824 comment('Move partition %s from default to %s' %
2825 (p, u.tgt_group))
2826 append('move %s %s' % (p, u.tgt_group))