blob: 67560495b4659fb62f96d625ae5a878bb56e1307 [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',
103 'system_ext', 'vendor')
Tao Bao9dd909e2017-11-14 11:27:32 -0800104
Tao Bao08c190f2019-06-03 23:07:58 -0700105# Chained VBMeta partitions.
106AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
107
Tianjie Xu861f4132018-09-12 11:49:33 -0700108# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900109PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700110
111
Tianjie Xu209db462016-05-24 17:34:52 -0700112class ErrorCode(object):
113 """Define error_codes for failures that happen during the actual
114 update package installation.
115
116 Error codes 0-999 are reserved for failures before the package
117 installation (i.e. low battery, package verification failure).
118 Detailed code in 'bootable/recovery/error_code.h' """
119
120 SYSTEM_VERIFICATION_FAILURE = 1000
121 SYSTEM_UPDATE_FAILURE = 1001
122 SYSTEM_UNEXPECTED_CONTENTS = 1002
123 SYSTEM_NONZERO_CONTENTS = 1003
124 SYSTEM_RECOVER_FAILURE = 1004
125 VENDOR_VERIFICATION_FAILURE = 2000
126 VENDOR_UPDATE_FAILURE = 2001
127 VENDOR_UNEXPECTED_CONTENTS = 2002
128 VENDOR_NONZERO_CONTENTS = 2003
129 VENDOR_RECOVER_FAILURE = 2004
130 OEM_PROP_MISMATCH = 3000
131 FINGERPRINT_MISMATCH = 3001
132 THUMBPRINT_MISMATCH = 3002
133 OLDER_BUILD = 3003
134 DEVICE_MISMATCH = 3004
135 BAD_PATCH_FILE = 3005
136 INSUFFICIENT_CACHE_SPACE = 3006
137 TUNE_PARTITION_FAILURE = 3007
138 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800139
Tao Bao80921982018-03-21 21:02:19 -0700140
Dan Albert8b72aef2015-03-23 19:13:21 -0700141class ExternalError(RuntimeError):
142 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700143
144
Tao Bao32fcdab2018-10-12 10:30:39 -0700145def InitLogging():
146 DEFAULT_LOGGING_CONFIG = {
147 'version': 1,
148 'disable_existing_loggers': False,
149 'formatters': {
150 'standard': {
151 'format':
152 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
153 'datefmt': '%Y-%m-%d %H:%M:%S',
154 },
155 },
156 'handlers': {
157 'default': {
158 'class': 'logging.StreamHandler',
159 'formatter': 'standard',
160 },
161 },
162 'loggers': {
163 '': {
164 'handlers': ['default'],
165 'level': 'WARNING',
166 'propagate': True,
167 }
168 }
169 }
170 env_config = os.getenv('LOGGING_CONFIG')
171 if env_config:
172 with open(env_config) as f:
173 config = json.load(f)
174 else:
175 config = DEFAULT_LOGGING_CONFIG
176
177 # Increase the logging level for verbose mode.
178 if OPTIONS.verbose:
179 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
180 config['loggers']['']['level'] = 'INFO'
181
182 logging.config.dictConfig(config)
183
184
Tao Bao39451582017-05-04 11:10:47 -0700185def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700186 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700187
Tao Bao73dd4f42018-10-04 16:25:33 -0700188 Args:
189 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700190 verbose: Whether the commands should be shown. Default to the global
191 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700192 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
193 stdin, etc. stdout and stderr will default to subprocess.PIPE and
194 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800195 universal_newlines will default to True, as most of the users in
196 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700197
198 Returns:
199 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700200 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700201 if 'stdout' not in kwargs and 'stderr' not in kwargs:
202 kwargs['stdout'] = subprocess.PIPE
203 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800204 if 'universal_newlines' not in kwargs:
205 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700206 # Don't log any if caller explicitly says so.
207 if verbose != False:
208 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700209 return subprocess.Popen(args, **kwargs)
210
211
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800212def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800213 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800214
215 Args:
216 args: The command represented as a list of strings.
217 verbose: Whether the commands should be shown. Default to the global
218 verbosity if unspecified.
219 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
220 stdin, etc. stdout and stderr will default to subprocess.PIPE and
221 subprocess.STDOUT respectively unless caller specifies any of them.
222
Bill Peckham889b0c62019-02-21 18:53:37 -0800223 Raises:
224 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800225 """
226 proc = Run(args, verbose=verbose, **kwargs)
227 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800228
229 if proc.returncode != 0:
230 raise ExternalError(
231 "Failed to run command '{}' (exit code {})".format(
232 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800233
234
Tao Bao986ee862018-10-04 15:46:16 -0700235def RunAndCheckOutput(args, verbose=None, **kwargs):
236 """Runs the given command and returns the output.
237
238 Args:
239 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700240 verbose: Whether the commands should be shown. Default to the global
241 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700242 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
243 stdin, etc. stdout and stderr will default to subprocess.PIPE and
244 subprocess.STDOUT respectively unless caller specifies any of them.
245
246 Returns:
247 The output string.
248
249 Raises:
250 ExternalError: On non-zero exit from the command.
251 """
Tao Bao986ee862018-10-04 15:46:16 -0700252 proc = Run(args, verbose=verbose, **kwargs)
253 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800254 if output is None:
255 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700256 # Don't log any if caller explicitly says so.
257 if verbose != False:
258 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700259 if proc.returncode != 0:
260 raise ExternalError(
261 "Failed to run command '{}' (exit code {}):\n{}".format(
262 args, proc.returncode, output))
263 return output
264
265
Tao Baoc765cca2018-01-31 17:32:40 -0800266def RoundUpTo4K(value):
267 rounded_up = value + 4095
268 return rounded_up - (rounded_up % 4096)
269
270
Ying Wang7e6d4e42010-12-13 16:25:36 -0800271def CloseInheritedPipes():
272 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
273 before doing other work."""
274 if platform.system() != "Darwin":
275 return
276 for d in range(3, 1025):
277 try:
278 stat = os.fstat(d)
279 if stat is not None:
280 pipebit = stat[0] & 0x1000
281 if pipebit != 0:
282 os.close(d)
283 except OSError:
284 pass
285
286
Tao Bao410ad8b2018-08-24 12:08:38 -0700287def LoadInfoDict(input_file, repacking=False):
288 """Loads the key/value pairs from the given input target_files.
289
290 It reads `META/misc_info.txt` file in the target_files input, does sanity
291 checks and returns the parsed key/value pairs for to the given build. It's
292 usually called early when working on input target_files files, e.g. when
293 generating OTAs, or signing builds. Note that the function may be called
294 against an old target_files file (i.e. from past dessert releases). So the
295 property parsing needs to be backward compatible.
296
297 In a `META/misc_info.txt`, a few properties are stored as links to the files
298 in the PRODUCT_OUT directory. It works fine with the build system. However,
299 they are no longer available when (re)generating images from target_files zip.
300 When `repacking` is True, redirect these properties to the actual files in the
301 unzipped directory.
302
303 Args:
304 input_file: The input target_files file, which could be an open
305 zipfile.ZipFile instance, or a str for the dir that contains the files
306 unzipped from a target_files file.
307 repacking: Whether it's trying repack an target_files file after loading the
308 info dict (default: False). If so, it will rewrite a few loaded
309 properties (e.g. selinux_fc, root_dir) to point to the actual files in
310 target_files file. When doing repacking, `input_file` must be a dir.
311
312 Returns:
313 A dict that contains the parsed key/value pairs.
314
315 Raises:
316 AssertionError: On invalid input arguments.
317 ValueError: On malformed input values.
318 """
319 if repacking:
320 assert isinstance(input_file, str), \
321 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700322
Doug Zongkerc9253822014-02-04 12:17:58 -0800323 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700324 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800325 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800326 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700327 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800328 try:
329 with open(path) as f:
330 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700331 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800332 if e.errno == errno.ENOENT:
333 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800334
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700335 try:
Michael Runge6e836112014-04-15 17:40:21 -0700336 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700337 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700338 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700339
Tao Bao410ad8b2018-08-24 12:08:38 -0700340 if "recovery_api_version" not in d:
341 raise ValueError("Failed to find 'recovery_api_version'")
342 if "fstab_version" not in d:
343 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800344
Tao Bao410ad8b2018-08-24 12:08:38 -0700345 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700346 # "selinux_fc" properties should point to the file_contexts files
347 # (file_contexts.bin) under META/.
348 for key in d:
349 if key.endswith("selinux_fc"):
350 fc_basename = os.path.basename(d[key])
351 fc_config = os.path.join(input_file, "META", fc_basename)
352 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700353
Daniel Norman72c626f2019-05-13 15:58:14 -0700354 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700355
Tom Cherryd14b8952018-08-09 14:26:00 -0700356 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700357 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700358 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700359 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700360
Tao Baof54216f2016-03-29 15:12:37 -0700361 # Redirect {system,vendor}_base_fs_file.
362 if "system_base_fs_file" in d:
363 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700364 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700365 if os.path.exists(system_base_fs_file):
366 d["system_base_fs_file"] = system_base_fs_file
367 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700368 logger.warning(
369 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700370 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700371
372 if "vendor_base_fs_file" in d:
373 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700374 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700375 if os.path.exists(vendor_base_fs_file):
376 d["vendor_base_fs_file"] = vendor_base_fs_file
377 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700378 logger.warning(
379 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700380 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700381
Doug Zongker37974732010-09-16 17:44:38 -0700382 def makeint(key):
383 if key in d:
384 d[key] = int(d[key], 0)
385
386 makeint("recovery_api_version")
387 makeint("blocksize")
388 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700389 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700390 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700391 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700392 makeint("recovery_size")
393 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800394 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700395
Tao Baoa57ab9f2018-08-24 12:08:38 -0700396 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
397 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
398 # cases, since it may load the info_dict from an old build (e.g. when
399 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800400 system_root_image = d.get("system_root_image") == "true"
401 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700402 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700403 if isinstance(input_file, zipfile.ZipFile):
404 if recovery_fstab_path not in input_file.namelist():
405 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
406 else:
407 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
408 if not os.path.exists(path):
409 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800410 d["fstab"] = LoadRecoveryFSTab(
411 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700412
Tao Bao76def242017-11-21 09:25:31 -0800413 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700414 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700415 if isinstance(input_file, zipfile.ZipFile):
416 if recovery_fstab_path not in input_file.namelist():
417 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
418 else:
419 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
420 if not os.path.exists(path):
421 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800422 d["fstab"] = LoadRecoveryFSTab(
423 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700424
Tianjie Xucfa86222016-03-07 16:31:19 -0800425 else:
426 d["fstab"] = None
427
Tianjie Xu861f4132018-09-12 11:49:33 -0700428 # Tries to load the build props for all partitions with care_map, including
429 # system and vendor.
430 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800431 partition_prop = "{}.build.prop".format(partition)
432 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700433 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800434 # Some partition might use /<partition>/etc/build.prop as the new path.
435 # TODO: try new path first when majority of them switch to the new path.
436 if not d[partition_prop]:
437 d[partition_prop] = LoadBuildProp(
438 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700439 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800440
441 # Set up the salt (based on fingerprint or thumbprint) that will be used when
442 # adding AVB footer.
443 if d.get("avb_enable") == "true":
444 fp = None
445 if "build.prop" in d:
446 build_prop = d["build.prop"]
447 if "ro.build.fingerprint" in build_prop:
448 fp = build_prop["ro.build.fingerprint"]
449 elif "ro.build.thumbprint" in build_prop:
450 fp = build_prop["ro.build.thumbprint"]
451 if fp:
452 d["avb_salt"] = sha256(fp).hexdigest()
453
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700454 return d
455
Tao Baod1de6f32017-03-01 16:38:48 -0800456
Tao Baobcd1d162017-08-26 13:10:26 -0700457def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700458 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700459 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700460 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700461 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700462 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700463 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700464
Tao Baod1de6f32017-03-01 16:38:48 -0800465
Daniel Norman4cc9df62019-07-18 10:11:07 -0700466def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900467 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700468 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900469
Daniel Norman4cc9df62019-07-18 10:11:07 -0700470
471def LoadDictionaryFromFile(file_path):
472 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900473 return LoadDictionaryFromLines(lines)
474
475
Michael Runge6e836112014-04-15 17:40:21 -0700476def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700477 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700478 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700479 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700480 if not line or line.startswith("#"):
481 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700482 if "=" in line:
483 name, value = line.split("=", 1)
484 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700485 return d
486
Tao Baod1de6f32017-03-01 16:38:48 -0800487
Tianjie Xucfa86222016-03-07 16:31:19 -0800488def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
489 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700490 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800491 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700492 self.mount_point = mount_point
493 self.fs_type = fs_type
494 self.device = device
495 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700496 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700497
498 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800499 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700500 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700501 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700502 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700503
Tao Baod1de6f32017-03-01 16:38:48 -0800504 assert fstab_version == 2
505
506 d = {}
507 for line in data.split("\n"):
508 line = line.strip()
509 if not line or line.startswith("#"):
510 continue
511
512 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
513 pieces = line.split()
514 if len(pieces) != 5:
515 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
516
517 # Ignore entries that are managed by vold.
518 options = pieces[4]
519 if "voldmanaged=" in options:
520 continue
521
522 # It's a good line, parse it.
523 length = 0
524 options = options.split(",")
525 for i in options:
526 if i.startswith("length="):
527 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800528 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800529 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700530 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800531
Tao Baod1de6f32017-03-01 16:38:48 -0800532 mount_flags = pieces[3]
533 # Honor the SELinux context if present.
534 context = None
535 for i in mount_flags.split(","):
536 if i.startswith("context="):
537 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800538
Tao Baod1de6f32017-03-01 16:38:48 -0800539 mount_point = pieces[1]
540 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
541 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800542
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700543 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700544 # system. Other areas assume system is always at "/system" so point /system
545 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700546 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800547 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700548 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700549 return d
550
551
Doug Zongker37974732010-09-16 17:44:38 -0700552def DumpInfoDict(d):
553 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700554 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700555
Dan Albert8b72aef2015-03-23 19:13:21 -0700556
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700557def MergeDynamicPartitionInfoDicts(framework_dict,
558 vendor_dict,
559 include_dynamic_partition_list=True,
560 size_prefix="",
561 size_suffix="",
562 list_prefix="",
563 list_suffix=""):
564 """Merges dynamic partition info variables.
565
566 Args:
567 framework_dict: The dictionary of dynamic partition info variables from the
568 partial framework target files.
569 vendor_dict: The dictionary of dynamic partition info variables from the
570 partial vendor target files.
571 include_dynamic_partition_list: If true, merges the dynamic_partition_list
572 variable. Not all use cases need this variable merged.
573 size_prefix: The prefix in partition group size variables that precedes the
574 name of the partition group. For example, partition group 'group_a' with
575 corresponding size variable 'super_group_a_group_size' would have the
576 size_prefix 'super_'.
577 size_suffix: Similar to size_prefix but for the variable's suffix. For
578 example, 'super_group_a_group_size' would have size_suffix '_group_size'.
579 list_prefix: Similar to size_prefix but for the partition group's
580 partition_list variable.
581 list_suffix: Similar to size_suffix but for the partition group's
582 partition_list variable.
583
584 Returns:
585 The merged dynamic partition info dictionary.
586 """
587 merged_dict = {}
588 # Partition groups and group sizes are defined by the vendor dict because
589 # these values may vary for each board that uses a shared system image.
590 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
591 if include_dynamic_partition_list:
592 framework_dynamic_partition_list = framework_dict.get(
593 "dynamic_partition_list", "")
594 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list",
595 "")
596 merged_dict["dynamic_partition_list"] = (
597 "%s %s" % (framework_dynamic_partition_list,
598 vendor_dynamic_partition_list)).strip()
599 for partition_group in merged_dict["super_partition_groups"].split(" "):
600 # Set the partition group's size using the value from the vendor dict.
601 key = "%s%s%s" % (size_prefix, partition_group, size_suffix)
602 if key not in vendor_dict:
603 raise ValueError("Vendor dict does not contain required key %s." % key)
604 merged_dict[key] = vendor_dict[key]
605
606 # Set the partition group's partition list using a concatenation of the
607 # framework and vendor partition lists.
608 key = "%s%s%s" % (list_prefix, partition_group, list_suffix)
609 merged_dict[key] = (
610 "%s %s" %
611 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
612 return merged_dict
613
614
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800615def AppendAVBSigningArgs(cmd, partition):
616 """Append signing arguments for avbtool."""
617 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
618 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700619 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
620 new_key_path = os.path.join(OPTIONS.search_path, key_path)
621 if os.path.exists(new_key_path):
622 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800623 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
624 if key_path and algorithm:
625 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700626 avb_salt = OPTIONS.info_dict.get("avb_salt")
627 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700628 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700629 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800630
631
Daniel Norman276f0622019-07-26 14:13:51 -0700632def GetAvbPartitionArg(partition, image, info_dict = None):
633 """Returns the VBMeta arguments for partition.
634
635 It sets up the VBMeta argument by including the partition descriptor from the
636 given 'image', or by configuring the partition as a chained partition.
637
638 Args:
639 partition: The name of the partition (e.g. "system").
640 image: The path to the partition image.
641 info_dict: A dict returned by common.LoadInfoDict(). Will use
642 OPTIONS.info_dict if None has been given.
643
644 Returns:
645 A list of VBMeta arguments.
646 """
647 if info_dict is None:
648 info_dict = OPTIONS.info_dict
649
650 # Check if chain partition is used.
651 key_path = info_dict.get("avb_" + partition + "_key_path")
652 if key_path:
653 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
654 return ["--chain_partition", chained_partition_arg]
655 else:
656 return ["--include_descriptors_from_image", image]
657
658
Tao Bao02a08592018-07-22 12:40:45 -0700659def GetAvbChainedPartitionArg(partition, info_dict, key=None):
660 """Constructs and returns the arg to build or verify a chained partition.
661
662 Args:
663 partition: The partition name.
664 info_dict: The info dict to look up the key info and rollback index
665 location.
666 key: The key to be used for building or verifying the partition. Defaults to
667 the key listed in info_dict.
668
669 Returns:
670 A string of form "partition:rollback_index_location:key" that can be used to
671 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700672 """
673 if key is None:
674 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700675 if key and not os.path.exists(key) and OPTIONS.search_path:
676 new_key_path = os.path.join(OPTIONS.search_path, key)
677 if os.path.exists(new_key_path):
678 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700679 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700680 rollback_index_location = info_dict[
681 "avb_" + partition + "_rollback_index_location"]
682 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
683
684
Daniel Norman276f0622019-07-26 14:13:51 -0700685def BuildVBMeta(image_path, partitions, name, needed_partitions):
686 """Creates a VBMeta image.
687
688 It generates the requested VBMeta image. The requested image could be for
689 top-level or chained VBMeta image, which is determined based on the name.
690
691 Args:
692 image_path: The output path for the new VBMeta image.
693 partitions: A dict that's keyed by partition names with image paths as
694 values. Only valid partition names are accepted, as listed in
695 common.AVB_PARTITIONS.
696 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
697 needed_partitions: Partitions whose descriptors should be included into the
698 generated VBMeta image.
699
700 Raises:
701 AssertionError: On invalid input args.
702 """
703 avbtool = OPTIONS.info_dict["avb_avbtool"]
704 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
705 AppendAVBSigningArgs(cmd, name)
706
707 for partition, path in partitions.items():
708 if partition not in needed_partitions:
709 continue
710 assert (partition in AVB_PARTITIONS or
711 partition in AVB_VBMETA_PARTITIONS), \
712 'Unknown partition: {}'.format(partition)
713 assert os.path.exists(path), \
714 'Failed to find {} for {}'.format(path, partition)
715 cmd.extend(GetAvbPartitionArg(partition, path))
716
717 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
718 if args and args.strip():
719 split_args = shlex.split(args)
720 for index, arg in enumerate(split_args[:-1]):
721 # Sanity check that the image file exists. Some images might be defined
722 # as a path relative to source tree, which may not be available at the
723 # same location when running this script (we have the input target_files
724 # zip only). For such cases, we additionally scan other locations (e.g.
725 # IMAGES/, RADIO/, etc) before bailing out.
726 if arg == '--include_descriptors_from_image':
727 image_path = split_args[index + 1]
728 if os.path.exists(image_path):
729 continue
730 found = False
731 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
732 alt_path = os.path.join(
733 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
734 if os.path.exists(alt_path):
735 split_args[index + 1] = alt_path
736 found = True
737 break
738 assert found, 'Failed to find {}'.format(image_path)
739 cmd.extend(split_args)
740
741 RunAndCheckOutput(cmd)
742
743
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700744def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800745 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700746 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700747
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700748 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800749 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
750 we are building a two-step special image (i.e. building a recovery image to
751 be loaded into /boot in two-step OTAs).
752
753 Return the image data, or None if sourcedir does not appear to contains files
754 for building the requested image.
755 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700756
757 def make_ramdisk():
758 ramdisk_img = tempfile.NamedTemporaryFile()
759
760 if os.access(fs_config_file, os.F_OK):
761 cmd = ["mkbootfs", "-f", fs_config_file,
762 os.path.join(sourcedir, "RAMDISK")]
763 else:
764 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
765 p1 = Run(cmd, stdout=subprocess.PIPE)
766 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
767
768 p2.wait()
769 p1.wait()
770 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
771 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
772
773 return ramdisk_img
774
775 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
776 return None
777
778 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700779 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700780
Doug Zongkerd5131602012-08-02 14:46:42 -0700781 if info_dict is None:
782 info_dict = OPTIONS.info_dict
783
Doug Zongkereef39442009-04-02 12:14:19 -0700784 img = tempfile.NamedTemporaryFile()
785
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700786 if has_ramdisk:
787 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700788
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800789 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
790 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
791
792 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700793
Benoit Fradina45a8682014-07-14 21:00:43 +0200794 fn = os.path.join(sourcedir, "second")
795 if os.access(fn, os.F_OK):
796 cmd.append("--second")
797 cmd.append(fn)
798
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800799 fn = os.path.join(sourcedir, "dtb")
800 if os.access(fn, os.F_OK):
801 cmd.append("--dtb")
802 cmd.append(fn)
803
Doug Zongker171f1cd2009-06-15 22:36:37 -0700804 fn = os.path.join(sourcedir, "cmdline")
805 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700806 cmd.append("--cmdline")
807 cmd.append(open(fn).read().rstrip("\n"))
808
809 fn = os.path.join(sourcedir, "base")
810 if os.access(fn, os.F_OK):
811 cmd.append("--base")
812 cmd.append(open(fn).read().rstrip("\n"))
813
Ying Wang4de6b5b2010-08-25 14:29:34 -0700814 fn = os.path.join(sourcedir, "pagesize")
815 if os.access(fn, os.F_OK):
816 cmd.append("--pagesize")
817 cmd.append(open(fn).read().rstrip("\n"))
818
Tao Bao76def242017-11-21 09:25:31 -0800819 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700820 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700821 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700822
Tao Bao76def242017-11-21 09:25:31 -0800823 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000824 if args and args.strip():
825 cmd.extend(shlex.split(args))
826
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700827 if has_ramdisk:
828 cmd.extend(["--ramdisk", ramdisk_img.name])
829
Tao Baod95e9fd2015-03-29 23:07:41 -0700830 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800831 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700832 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700833 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700834 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700835 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700836
Tao Baobf70c3182017-07-11 17:27:55 -0700837 # "boot" or "recovery", without extension.
838 partition_name = os.path.basename(sourcedir).lower()
839
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800840 if partition_name == "recovery":
841 if info_dict.get("include_recovery_dtbo") == "true":
842 fn = os.path.join(sourcedir, "recovery_dtbo")
843 cmd.extend(["--recovery_dtbo", fn])
844 if info_dict.get("include_recovery_acpio") == "true":
845 fn = os.path.join(sourcedir, "recovery_acpio")
846 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700847
Tao Bao986ee862018-10-04 15:46:16 -0700848 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700849
Tao Bao76def242017-11-21 09:25:31 -0800850 if (info_dict.get("boot_signer") == "true" and
851 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800852 # Hard-code the path as "/boot" for two-step special recovery image (which
853 # will be loaded into /boot during the two-step OTA).
854 if two_step_image:
855 path = "/boot"
856 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700857 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700858 cmd = [OPTIONS.boot_signer_path]
859 cmd.extend(OPTIONS.boot_signer_args)
860 cmd.extend([path, img.name,
861 info_dict["verity_key"] + ".pk8",
862 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700863 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700864
Tao Baod95e9fd2015-03-29 23:07:41 -0700865 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800866 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700867 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700868 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800869 # We have switched from the prebuilt futility binary to using the tool
870 # (futility-host) built from the source. Override the setting in the old
871 # TF.zip.
872 futility = info_dict["futility"]
873 if futility.startswith("prebuilts/"):
874 futility = "futility-host"
875 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700876 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700877 info_dict["vboot_key"] + ".vbprivk",
878 info_dict["vboot_subkey"] + ".vbprivk",
879 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700880 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700881 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700882
Tao Baof3282b42015-04-01 11:21:55 -0700883 # Clean up the temp files.
884 img_unsigned.close()
885 img_keyblock.close()
886
David Zeuthen8fecb282017-12-01 16:24:01 -0500887 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800888 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700889 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500890 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400891 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700892 "--partition_size", str(part_size), "--partition_name",
893 partition_name]
894 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500895 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400896 if args and args.strip():
897 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700898 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500899
900 img.seek(os.SEEK_SET, 0)
901 data = img.read()
902
903 if has_ramdisk:
904 ramdisk_img.close()
905 img.close()
906
907 return data
908
909
Doug Zongkerd5131602012-08-02 14:46:42 -0700910def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800911 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700912 """Return a File object with the desired bootable image.
913
914 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
915 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
916 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700917
Doug Zongker55d93282011-01-25 17:03:34 -0800918 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
919 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700920 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800921 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700922
923 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
924 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700925 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700926 return File.FromLocalFile(name, prebuilt_path)
927
Tao Bao32fcdab2018-10-12 10:30:39 -0700928 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700929
930 if info_dict is None:
931 info_dict = OPTIONS.info_dict
932
933 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800934 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
935 # for recovery.
936 has_ramdisk = (info_dict.get("system_root_image") != "true" or
937 prebuilt_name != "boot.img" or
938 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700939
Doug Zongker6f1d0312014-08-22 08:07:12 -0700940 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400941 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
942 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800943 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700944 if data:
945 return File(name, data)
946 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800947
Doug Zongkereef39442009-04-02 12:14:19 -0700948
Narayan Kamatha07bf042017-08-14 14:49:21 +0100949def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800950 """Gunzips the given gzip compressed file to a given output file."""
951 with gzip.open(in_filename, "rb") as in_file, \
952 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100953 shutil.copyfileobj(in_file, out_file)
954
955
Tao Bao0ff15de2019-03-20 11:26:06 -0700956def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800957 """Unzips the archive to the given directory.
958
959 Args:
960 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800961 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700962 patterns: Files to unzip from the archive. If omitted, will unzip the entire
963 archvie. Non-matching patterns will be filtered out. If there's no match
964 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800965 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800966 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700967 if patterns is not None:
968 # Filter out non-matching patterns. unzip will complain otherwise.
969 with zipfile.ZipFile(filename) as input_zip:
970 names = input_zip.namelist()
971 filtered = [
972 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
973
974 # There isn't any matching files. Don't unzip anything.
975 if not filtered:
976 return
977 cmd.extend(filtered)
978
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800979 RunAndCheckOutput(cmd)
980
981
Doug Zongker75f17362009-12-08 13:46:44 -0800982def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800983 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800984
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800985 Args:
986 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
987 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
988
989 pattern: Files to unzip from the archive. If omitted, will unzip the entire
990 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800991
Tao Bao1c830bf2017-12-25 10:43:47 -0800992 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800993 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800994 """
Doug Zongkereef39442009-04-02 12:14:19 -0700995
Tao Bao1c830bf2017-12-25 10:43:47 -0800996 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800997 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
998 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800999 UnzipToDir(m.group(1), tmp, pattern)
1000 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001001 filename = m.group(1)
1002 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001003 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001004
Tao Baodba59ee2018-01-09 13:21:02 -08001005 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001006
1007
Yifan Hong8a66a712019-04-04 15:37:57 -07001008def GetUserImage(which, tmpdir, input_zip,
1009 info_dict=None,
1010 allow_shared_blocks=None,
1011 hashtree_info_generator=None,
1012 reset_file_map=False):
1013 """Returns an Image object suitable for passing to BlockImageDiff.
1014
1015 This function loads the specified image from the given path. If the specified
1016 image is sparse, it also performs additional processing for OTA purpose. For
1017 example, it always adds block 0 to clobbered blocks list. It also detects
1018 files that cannot be reconstructed from the block list, for whom we should
1019 avoid applying imgdiff.
1020
1021 Args:
1022 which: The partition name.
1023 tmpdir: The directory that contains the prebuilt image and block map file.
1024 input_zip: The target-files ZIP archive.
1025 info_dict: The dict to be looked up for relevant info.
1026 allow_shared_blocks: If image is sparse, whether having shared blocks is
1027 allowed. If none, it is looked up from info_dict.
1028 hashtree_info_generator: If present and image is sparse, generates the
1029 hashtree_info for this sparse image.
1030 reset_file_map: If true and image is sparse, reset file map before returning
1031 the image.
1032 Returns:
1033 A Image object. If it is a sparse image and reset_file_map is False, the
1034 image will have file_map info loaded.
1035 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001036 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001037 info_dict = LoadInfoDict(input_zip)
1038
1039 is_sparse = info_dict.get("extfs_sparse_flag")
1040
1041 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1042 # shared blocks (i.e. some blocks will show up in multiple files' block
1043 # list). We can only allocate such shared blocks to the first "owner", and
1044 # disable imgdiff for all later occurrences.
1045 if allow_shared_blocks is None:
1046 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1047
1048 if is_sparse:
1049 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1050 hashtree_info_generator)
1051 if reset_file_map:
1052 img.ResetFileMap()
1053 return img
1054 else:
1055 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1056
1057
1058def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1059 """Returns a Image object suitable for passing to BlockImageDiff.
1060
1061 This function loads the specified non-sparse image from the given path.
1062
1063 Args:
1064 which: The partition name.
1065 tmpdir: The directory that contains the prebuilt image and block map file.
1066 Returns:
1067 A Image object.
1068 """
1069 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1070 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1071
1072 # The image and map files must have been created prior to calling
1073 # ota_from_target_files.py (since LMP).
1074 assert os.path.exists(path) and os.path.exists(mappath)
1075
Tianjie Xu41976c72019-07-03 13:57:01 -07001076 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1077
Yifan Hong8a66a712019-04-04 15:37:57 -07001078
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001079def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1080 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001081 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1082
1083 This function loads the specified sparse image from the given path, and
1084 performs additional processing for OTA purpose. For example, it always adds
1085 block 0 to clobbered blocks list. It also detects files that cannot be
1086 reconstructed from the block list, for whom we should avoid applying imgdiff.
1087
1088 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001089 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001090 tmpdir: The directory that contains the prebuilt image and block map file.
1091 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001092 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001093 hashtree_info_generator: If present, generates the hashtree_info for this
1094 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001095 Returns:
1096 A SparseImage object, with file_map info loaded.
1097 """
Tao Baoc765cca2018-01-31 17:32:40 -08001098 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1099 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1100
1101 # The image and map files must have been created prior to calling
1102 # ota_from_target_files.py (since LMP).
1103 assert os.path.exists(path) and os.path.exists(mappath)
1104
1105 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1106 # it to clobbered_blocks so that it will be written to the target
1107 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1108 clobbered_blocks = "0"
1109
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001110 image = sparse_img.SparseImage(
1111 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1112 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001113
1114 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1115 # if they contain all zeros. We can't reconstruct such a file from its block
1116 # list. Tag such entries accordingly. (Bug: 65213616)
1117 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001118 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001119 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001120 continue
1121
Tom Cherryd14b8952018-08-09 14:26:00 -07001122 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1123 # filename listed in system.map may contain an additional leading slash
1124 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1125 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001126 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001127
Tom Cherryd14b8952018-08-09 14:26:00 -07001128 # Special handling another case, where files not under /system
1129 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001130 if which == 'system' and not arcname.startswith('SYSTEM'):
1131 arcname = 'ROOT/' + arcname
1132
1133 assert arcname in input_zip.namelist(), \
1134 "Failed to find the ZIP entry for {}".format(entry)
1135
Tao Baoc765cca2018-01-31 17:32:40 -08001136 info = input_zip.getinfo(arcname)
1137 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001138
1139 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001140 # image, check the original block list to determine its completeness. Note
1141 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001142 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001143 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001144
Tao Baoc765cca2018-01-31 17:32:40 -08001145 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1146 ranges.extra['incomplete'] = True
1147
1148 return image
1149
1150
Doug Zongkereef39442009-04-02 12:14:19 -07001151def GetKeyPasswords(keylist):
1152 """Given a list of keys, prompt the user to enter passwords for
1153 those which require them. Return a {key: password} dict. password
1154 will be None if the key has no password."""
1155
Doug Zongker8ce7c252009-05-22 13:34:54 -07001156 no_passwords = []
1157 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001158 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001159 devnull = open("/dev/null", "w+b")
1160 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001161 # We don't need a password for things that aren't really keys.
1162 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001163 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001164 continue
1165
T.R. Fullhart37e10522013-03-18 10:31:26 -07001166 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001167 "-inform", "DER", "-nocrypt"],
1168 stdin=devnull.fileno(),
1169 stdout=devnull.fileno(),
1170 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001171 p.communicate()
1172 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001173 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001174 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001175 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001176 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1177 "-inform", "DER", "-passin", "pass:"],
1178 stdin=devnull.fileno(),
1179 stdout=devnull.fileno(),
1180 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001181 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001182 if p.returncode == 0:
1183 # Encrypted key with empty string as password.
1184 key_passwords[k] = ''
1185 elif stderr.startswith('Error decrypting key'):
1186 # Definitely encrypted key.
1187 # It would have said "Error reading key" if it didn't parse correctly.
1188 need_passwords.append(k)
1189 else:
1190 # Potentially, a type of key that openssl doesn't understand.
1191 # We'll let the routines in signapk.jar handle it.
1192 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001193 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001194
T.R. Fullhart37e10522013-03-18 10:31:26 -07001195 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001196 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001197 return key_passwords
1198
1199
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001200def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001201 """Gets the minSdkVersion declared in the APK.
1202
changho.shin0f125362019-07-08 10:59:00 +09001203 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001204 This can be both a decimal number (API Level) or a codename.
1205
1206 Args:
1207 apk_name: The APK filename.
1208
1209 Returns:
1210 The parsed SDK version string.
1211
1212 Raises:
1213 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001214 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001215 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001216 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001217 stderr=subprocess.PIPE)
1218 stdoutdata, stderrdata = proc.communicate()
1219 if proc.returncode != 0:
1220 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001221 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001222 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001223
Tao Baof47bf0f2018-03-21 23:28:51 -07001224 for line in stdoutdata.split("\n"):
1225 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001226 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1227 if m:
1228 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001229 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001230
1231
1232def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001233 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001234
Tao Baof47bf0f2018-03-21 23:28:51 -07001235 If minSdkVersion is set to a codename, it is translated to a number using the
1236 provided map.
1237
1238 Args:
1239 apk_name: The APK filename.
1240
1241 Returns:
1242 The parsed SDK version number.
1243
1244 Raises:
1245 ExternalError: On failing to get the min SDK version number.
1246 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001247 version = GetMinSdkVersion(apk_name)
1248 try:
1249 return int(version)
1250 except ValueError:
1251 # Not a decimal number. Codename?
1252 if version in codename_to_api_level_map:
1253 return codename_to_api_level_map[version]
1254 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001255 raise ExternalError(
1256 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1257 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001258
1259
1260def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001261 codename_to_api_level_map=None, whole_file=False,
1262 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001263 """Sign the input_name zip/jar/apk, producing output_name. Use the
1264 given key and password (the latter may be None if the key does not
1265 have a password.
1266
Doug Zongker951495f2009-08-14 12:44:19 -07001267 If whole_file is true, use the "-w" option to SignApk to embed a
1268 signature that covers the whole file in the archive comment of the
1269 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001270
1271 min_api_level is the API Level (int) of the oldest platform this file may end
1272 up on. If not specified for an APK, the API Level is obtained by interpreting
1273 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1274
1275 codename_to_api_level_map is needed to translate the codename which may be
1276 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001277
1278 Caller may optionally specify extra args to be passed to SignApk, which
1279 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001280 """
Tao Bao76def242017-11-21 09:25:31 -08001281 if codename_to_api_level_map is None:
1282 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001283 if extra_signapk_args is None:
1284 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001285
Alex Klyubin9667b182015-12-10 13:38:50 -08001286 java_library_path = os.path.join(
1287 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1288
Tao Baoe95540e2016-11-08 12:08:53 -08001289 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1290 ["-Djava.library.path=" + java_library_path,
1291 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001292 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001293 if whole_file:
1294 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001295
1296 min_sdk_version = min_api_level
1297 if min_sdk_version is None:
1298 if not whole_file:
1299 min_sdk_version = GetMinSdkVersionInt(
1300 input_name, codename_to_api_level_map)
1301 if min_sdk_version is not None:
1302 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1303
T.R. Fullhart37e10522013-03-18 10:31:26 -07001304 cmd.extend([key + OPTIONS.public_key_suffix,
1305 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001306 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001307
Tao Bao73dd4f42018-10-04 16:25:33 -07001308 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001309 if password is not None:
1310 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001311 stdoutdata, _ = proc.communicate(password)
1312 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001313 raise ExternalError(
1314 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001315 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001316
Doug Zongkereef39442009-04-02 12:14:19 -07001317
Doug Zongker37974732010-09-16 17:44:38 -07001318def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001319 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001320
Tao Bao9dd909e2017-11-14 11:27:32 -08001321 For non-AVB images, raise exception if the data is too big. Print a warning
1322 if the data is nearing the maximum size.
1323
1324 For AVB images, the actual image size should be identical to the limit.
1325
1326 Args:
1327 data: A string that contains all the data for the partition.
1328 target: The partition name. The ".img" suffix is optional.
1329 info_dict: The dict to be looked up for relevant info.
1330 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001331 if target.endswith(".img"):
1332 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001333 mount_point = "/" + target
1334
Ying Wangf8824af2014-06-03 14:07:27 -07001335 fs_type = None
1336 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001337 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001338 if mount_point == "/userdata":
1339 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001340 p = info_dict["fstab"][mount_point]
1341 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001342 device = p.device
1343 if "/" in device:
1344 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001345 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001346 if not fs_type or not limit:
1347 return
Doug Zongkereef39442009-04-02 12:14:19 -07001348
Andrew Boie0f9aec82012-02-14 09:32:52 -08001349 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001350 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1351 # path.
1352 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1353 if size != limit:
1354 raise ExternalError(
1355 "Mismatching image size for %s: expected %d actual %d" % (
1356 target, limit, size))
1357 else:
1358 pct = float(size) * 100.0 / limit
1359 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1360 if pct >= 99.0:
1361 raise ExternalError(msg)
1362 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001363 logger.warning("\n WARNING: %s\n", msg)
1364 else:
1365 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001366
1367
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001368def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001369 """Parses the APK certs info from a given target-files zip.
1370
1371 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1372 tuple with the following elements: (1) a dictionary that maps packages to
1373 certs (based on the "certificate" and "private_key" attributes in the file;
1374 (2) a string representing the extension of compressed APKs in the target files
1375 (e.g ".gz", ".bro").
1376
1377 Args:
1378 tf_zip: The input target_files ZipFile (already open).
1379
1380 Returns:
1381 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1382 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1383 no compressed APKs.
1384 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001385 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001386 compressed_extension = None
1387
Tao Bao0f990332017-09-08 19:02:54 -07001388 # META/apkcerts.txt contains the info for _all_ the packages known at build
1389 # time. Filter out the ones that are not installed.
1390 installed_files = set()
1391 for name in tf_zip.namelist():
1392 basename = os.path.basename(name)
1393 if basename:
1394 installed_files.add(basename)
1395
Tao Baoda30cfa2017-12-01 16:19:46 -08001396 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001397 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001398 if not line:
1399 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001400 m = re.match(
1401 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1402 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1403 line)
1404 if not m:
1405 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001406
Tao Bao818ddf52018-01-05 11:17:34 -08001407 matches = m.groupdict()
1408 cert = matches["CERT"]
1409 privkey = matches["PRIVKEY"]
1410 name = matches["NAME"]
1411 this_compressed_extension = matches["COMPRESSED"]
1412
1413 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1414 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1415 if cert in SPECIAL_CERT_STRINGS and not privkey:
1416 certmap[name] = cert
1417 elif (cert.endswith(OPTIONS.public_key_suffix) and
1418 privkey.endswith(OPTIONS.private_key_suffix) and
1419 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1420 certmap[name] = cert[:-public_key_suffix_len]
1421 else:
1422 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1423
1424 if not this_compressed_extension:
1425 continue
1426
1427 # Only count the installed files.
1428 filename = name + '.' + this_compressed_extension
1429 if filename not in installed_files:
1430 continue
1431
1432 # Make sure that all the values in the compression map have the same
1433 # extension. We don't support multiple compression methods in the same
1434 # system image.
1435 if compressed_extension:
1436 if this_compressed_extension != compressed_extension:
1437 raise ValueError(
1438 "Multiple compressed extensions: {} vs {}".format(
1439 compressed_extension, this_compressed_extension))
1440 else:
1441 compressed_extension = this_compressed_extension
1442
1443 return (certmap,
1444 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001445
1446
Doug Zongkereef39442009-04-02 12:14:19 -07001447COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001448Global options
1449
1450 -p (--path) <dir>
1451 Prepend <dir>/bin to the list of places to search for binaries run by this
1452 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001453
Doug Zongker05d3dea2009-06-22 11:32:31 -07001454 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001455 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001456
Tao Bao30df8b42018-04-23 15:32:53 -07001457 -x (--extra) <key=value>
1458 Add a key/value pair to the 'extras' dict, which device-specific extension
1459 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001460
Doug Zongkereef39442009-04-02 12:14:19 -07001461 -v (--verbose)
1462 Show command lines being executed.
1463
1464 -h (--help)
1465 Display this usage message and exit.
1466"""
1467
1468def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001469 print(docstring.rstrip("\n"))
1470 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001471
1472
1473def ParseOptions(argv,
1474 docstring,
1475 extra_opts="", extra_long_opts=(),
1476 extra_option_handler=None):
1477 """Parse the options in argv and return any arguments that aren't
1478 flags. docstring is the calling module's docstring, to be displayed
1479 for errors and -h. extra_opts and extra_long_opts are for flags
1480 defined by the caller, which are processed by passing them to
1481 extra_option_handler."""
1482
1483 try:
1484 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001485 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001486 ["help", "verbose", "path=", "signapk_path=",
1487 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001488 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001489 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1490 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001491 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001492 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001493 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001494 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001495 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001496 sys.exit(2)
1497
Doug Zongkereef39442009-04-02 12:14:19 -07001498 for o, a in opts:
1499 if o in ("-h", "--help"):
1500 Usage(docstring)
1501 sys.exit()
1502 elif o in ("-v", "--verbose"):
1503 OPTIONS.verbose = True
1504 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001505 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001506 elif o in ("--signapk_path",):
1507 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001508 elif o in ("--signapk_shared_library_path",):
1509 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001510 elif o in ("--extra_signapk_args",):
1511 OPTIONS.extra_signapk_args = shlex.split(a)
1512 elif o in ("--java_path",):
1513 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001514 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001515 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001516 elif o in ("--public_key_suffix",):
1517 OPTIONS.public_key_suffix = a
1518 elif o in ("--private_key_suffix",):
1519 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001520 elif o in ("--boot_signer_path",):
1521 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001522 elif o in ("--boot_signer_args",):
1523 OPTIONS.boot_signer_args = shlex.split(a)
1524 elif o in ("--verity_signer_path",):
1525 OPTIONS.verity_signer_path = a
1526 elif o in ("--verity_signer_args",):
1527 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001528 elif o in ("-s", "--device_specific"):
1529 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001530 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001531 key, value = a.split("=", 1)
1532 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001533 else:
1534 if extra_option_handler is None or not extra_option_handler(o, a):
1535 assert False, "unknown option \"%s\"" % (o,)
1536
Doug Zongker85448772014-09-09 14:59:20 -07001537 if OPTIONS.search_path:
1538 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1539 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001540
1541 return args
1542
1543
Tao Bao4c851b12016-09-19 13:54:38 -07001544def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001545 """Make a temp file and add it to the list of things to be deleted
1546 when Cleanup() is called. Return the filename."""
1547 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1548 os.close(fd)
1549 OPTIONS.tempfiles.append(fn)
1550 return fn
1551
1552
Tao Bao1c830bf2017-12-25 10:43:47 -08001553def MakeTempDir(prefix='tmp', suffix=''):
1554 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1555
1556 Returns:
1557 The absolute pathname of the new directory.
1558 """
1559 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1560 OPTIONS.tempfiles.append(dir_name)
1561 return dir_name
1562
1563
Doug Zongkereef39442009-04-02 12:14:19 -07001564def Cleanup():
1565 for i in OPTIONS.tempfiles:
1566 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001567 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001568 else:
1569 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001570 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001571
1572
1573class PasswordManager(object):
1574 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001575 self.editor = os.getenv("EDITOR")
1576 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001577
1578 def GetPasswords(self, items):
1579 """Get passwords corresponding to each string in 'items',
1580 returning a dict. (The dict may have keys in addition to the
1581 values in 'items'.)
1582
1583 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1584 user edit that file to add more needed passwords. If no editor is
1585 available, or $ANDROID_PW_FILE isn't define, prompts the user
1586 interactively in the ordinary way.
1587 """
1588
1589 current = self.ReadFile()
1590
1591 first = True
1592 while True:
1593 missing = []
1594 for i in items:
1595 if i not in current or not current[i]:
1596 missing.append(i)
1597 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001598 if not missing:
1599 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001600
1601 for i in missing:
1602 current[i] = ""
1603
1604 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001605 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001606 if sys.version_info[0] >= 3:
1607 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001608 answer = raw_input("try to edit again? [y]> ").strip()
1609 if answer and answer[0] not in 'yY':
1610 raise RuntimeError("key passwords unavailable")
1611 first = False
1612
1613 current = self.UpdateAndReadFile(current)
1614
Dan Albert8b72aef2015-03-23 19:13:21 -07001615 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001616 """Prompt the user to enter a value (password) for each key in
1617 'current' whose value is fales. Returns a new dict with all the
1618 values.
1619 """
1620 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001621 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001622 if v:
1623 result[k] = v
1624 else:
1625 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001626 result[k] = getpass.getpass(
1627 "Enter password for %s key> " % k).strip()
1628 if result[k]:
1629 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001630 return result
1631
1632 def UpdateAndReadFile(self, current):
1633 if not self.editor or not self.pwfile:
1634 return self.PromptResult(current)
1635
1636 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001637 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001638 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1639 f.write("# (Additional spaces are harmless.)\n\n")
1640
1641 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001642 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001643 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001644 f.write("[[[ %s ]]] %s\n" % (v, k))
1645 if not v and first_line is None:
1646 # position cursor on first line with no password.
1647 first_line = i + 4
1648 f.close()
1649
Tao Bao986ee862018-10-04 15:46:16 -07001650 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001651
1652 return self.ReadFile()
1653
1654 def ReadFile(self):
1655 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001656 if self.pwfile is None:
1657 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001658 try:
1659 f = open(self.pwfile, "r")
1660 for line in f:
1661 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001662 if not line or line[0] == '#':
1663 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001664 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1665 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001666 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001667 else:
1668 result[m.group(2)] = m.group(1)
1669 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001670 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001671 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001672 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001673 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001674
1675
Dan Albert8e0178d2015-01-27 15:53:15 -08001676def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1677 compress_type=None):
1678 import datetime
1679
1680 # http://b/18015246
1681 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1682 # for files larger than 2GiB. We can work around this by adjusting their
1683 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1684 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1685 # it isn't clear to me exactly what circumstances cause this).
1686 # `zipfile.write()` must be used directly to work around this.
1687 #
1688 # This mess can be avoided if we port to python3.
1689 saved_zip64_limit = zipfile.ZIP64_LIMIT
1690 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1691
1692 if compress_type is None:
1693 compress_type = zip_file.compression
1694 if arcname is None:
1695 arcname = filename
1696
1697 saved_stat = os.stat(filename)
1698
1699 try:
1700 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1701 # file to be zipped and reset it when we're done.
1702 os.chmod(filename, perms)
1703
1704 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001705 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1706 # intentional. zip stores datetimes in local time without a time zone
1707 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1708 # in the zip archive.
1709 local_epoch = datetime.datetime.fromtimestamp(0)
1710 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001711 os.utime(filename, (timestamp, timestamp))
1712
1713 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1714 finally:
1715 os.chmod(filename, saved_stat.st_mode)
1716 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1717 zipfile.ZIP64_LIMIT = saved_zip64_limit
1718
1719
Tao Bao58c1b962015-05-20 09:32:18 -07001720def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001721 compress_type=None):
1722 """Wrap zipfile.writestr() function to work around the zip64 limit.
1723
1724 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1725 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1726 when calling crc32(bytes).
1727
1728 But it still works fine to write a shorter string into a large zip file.
1729 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1730 when we know the string won't be too long.
1731 """
1732
1733 saved_zip64_limit = zipfile.ZIP64_LIMIT
1734 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1735
1736 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1737 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001738 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001739 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001740 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001741 else:
Tao Baof3282b42015-04-01 11:21:55 -07001742 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001743 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1744 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1745 # such a case (since
1746 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1747 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1748 # permission bits. We follow the logic in Python 3 to get consistent
1749 # behavior between using the two versions.
1750 if not zinfo.external_attr:
1751 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001752
1753 # If compress_type is given, it overrides the value in zinfo.
1754 if compress_type is not None:
1755 zinfo.compress_type = compress_type
1756
Tao Bao58c1b962015-05-20 09:32:18 -07001757 # If perms is given, it has a priority.
1758 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001759 # If perms doesn't set the file type, mark it as a regular file.
1760 if perms & 0o770000 == 0:
1761 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001762 zinfo.external_attr = perms << 16
1763
Tao Baof3282b42015-04-01 11:21:55 -07001764 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001765 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1766
Dan Albert8b72aef2015-03-23 19:13:21 -07001767 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001768 zipfile.ZIP64_LIMIT = saved_zip64_limit
1769
1770
Tao Bao89d7ab22017-12-14 17:05:33 -08001771def ZipDelete(zip_filename, entries):
1772 """Deletes entries from a ZIP file.
1773
1774 Since deleting entries from a ZIP file is not supported, it shells out to
1775 'zip -d'.
1776
1777 Args:
1778 zip_filename: The name of the ZIP file.
1779 entries: The name of the entry, or the list of names to be deleted.
1780
1781 Raises:
1782 AssertionError: In case of non-zero return from 'zip'.
1783 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001784 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001785 entries = [entries]
1786 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001787 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001788
1789
Tao Baof3282b42015-04-01 11:21:55 -07001790def ZipClose(zip_file):
1791 # http://b/18015246
1792 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1793 # central directory.
1794 saved_zip64_limit = zipfile.ZIP64_LIMIT
1795 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1796
1797 zip_file.close()
1798
1799 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001800
1801
1802class DeviceSpecificParams(object):
1803 module = None
1804 def __init__(self, **kwargs):
1805 """Keyword arguments to the constructor become attributes of this
1806 object, which is passed to all functions in the device-specific
1807 module."""
Tao Bao38884282019-07-10 22:20:56 -07001808 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07001809 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001810 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001811
1812 if self.module is None:
1813 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001814 if not path:
1815 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001816 try:
1817 if os.path.isdir(path):
1818 info = imp.find_module("releasetools", [path])
1819 else:
1820 d, f = os.path.split(path)
1821 b, x = os.path.splitext(f)
1822 if x == ".py":
1823 f = b
1824 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001825 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001826 self.module = imp.load_module("device_specific", *info)
1827 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001828 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001829
1830 def _DoCall(self, function_name, *args, **kwargs):
1831 """Call the named function in the device-specific module, passing
1832 the given args and kwargs. The first argument to the call will be
1833 the DeviceSpecific object itself. If there is no module, or the
1834 module does not define the function, return the value of the
1835 'default' kwarg (which itself defaults to None)."""
1836 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001837 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001838 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1839
1840 def FullOTA_Assertions(self):
1841 """Called after emitting the block of assertions at the top of a
1842 full OTA package. Implementations can add whatever additional
1843 assertions they like."""
1844 return self._DoCall("FullOTA_Assertions")
1845
Doug Zongkere5ff5902012-01-17 10:55:37 -08001846 def FullOTA_InstallBegin(self):
1847 """Called at the start of full OTA installation."""
1848 return self._DoCall("FullOTA_InstallBegin")
1849
Yifan Hong10c530d2018-12-27 17:34:18 -08001850 def FullOTA_GetBlockDifferences(self):
1851 """Called during full OTA installation and verification.
1852 Implementation should return a list of BlockDifference objects describing
1853 the update on each additional partitions.
1854 """
1855 return self._DoCall("FullOTA_GetBlockDifferences")
1856
Doug Zongker05d3dea2009-06-22 11:32:31 -07001857 def FullOTA_InstallEnd(self):
1858 """Called at the end of full OTA installation; typically this is
1859 used to install the image for the device's baseband processor."""
1860 return self._DoCall("FullOTA_InstallEnd")
1861
1862 def IncrementalOTA_Assertions(self):
1863 """Called after emitting the block of assertions at the top of an
1864 incremental OTA package. Implementations can add whatever
1865 additional assertions they like."""
1866 return self._DoCall("IncrementalOTA_Assertions")
1867
Doug Zongkere5ff5902012-01-17 10:55:37 -08001868 def IncrementalOTA_VerifyBegin(self):
1869 """Called at the start of the verification phase of incremental
1870 OTA installation; additional checks can be placed here to abort
1871 the script before any changes are made."""
1872 return self._DoCall("IncrementalOTA_VerifyBegin")
1873
Doug Zongker05d3dea2009-06-22 11:32:31 -07001874 def IncrementalOTA_VerifyEnd(self):
1875 """Called at the end of the verification phase of incremental OTA
1876 installation; additional checks can be placed here to abort the
1877 script before any changes are made."""
1878 return self._DoCall("IncrementalOTA_VerifyEnd")
1879
Doug Zongkere5ff5902012-01-17 10:55:37 -08001880 def IncrementalOTA_InstallBegin(self):
1881 """Called at the start of incremental OTA installation (after
1882 verification is complete)."""
1883 return self._DoCall("IncrementalOTA_InstallBegin")
1884
Yifan Hong10c530d2018-12-27 17:34:18 -08001885 def IncrementalOTA_GetBlockDifferences(self):
1886 """Called during incremental OTA installation and verification.
1887 Implementation should return a list of BlockDifference objects describing
1888 the update on each additional partitions.
1889 """
1890 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1891
Doug Zongker05d3dea2009-06-22 11:32:31 -07001892 def IncrementalOTA_InstallEnd(self):
1893 """Called at the end of incremental OTA installation; typically
1894 this is used to install the image for the device's baseband
1895 processor."""
1896 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001897
Tao Bao9bc6bb22015-11-09 16:58:28 -08001898 def VerifyOTA_Assertions(self):
1899 return self._DoCall("VerifyOTA_Assertions")
1900
Tao Bao76def242017-11-21 09:25:31 -08001901
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001902class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001903 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001904 self.name = name
1905 self.data = data
1906 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001907 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001908 self.sha1 = sha1(data).hexdigest()
1909
1910 @classmethod
1911 def FromLocalFile(cls, name, diskname):
1912 f = open(diskname, "rb")
1913 data = f.read()
1914 f.close()
1915 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001916
1917 def WriteToTemp(self):
1918 t = tempfile.NamedTemporaryFile()
1919 t.write(self.data)
1920 t.flush()
1921 return t
1922
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001923 def WriteToDir(self, d):
1924 with open(os.path.join(d, self.name), "wb") as fp:
1925 fp.write(self.data)
1926
Geremy Condra36bd3652014-02-06 19:45:10 -08001927 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001928 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001929
Tao Bao76def242017-11-21 09:25:31 -08001930
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001931DIFF_PROGRAM_BY_EXT = {
1932 ".gz" : "imgdiff",
1933 ".zip" : ["imgdiff", "-z"],
1934 ".jar" : ["imgdiff", "-z"],
1935 ".apk" : ["imgdiff", "-z"],
1936 ".img" : "imgdiff",
1937 }
1938
Tao Bao76def242017-11-21 09:25:31 -08001939
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001940class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001941 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001942 self.tf = tf
1943 self.sf = sf
1944 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001945 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001946
1947 def ComputePatch(self):
1948 """Compute the patch (as a string of data) needed to turn sf into
1949 tf. Returns the same tuple as GetPatch()."""
1950
1951 tf = self.tf
1952 sf = self.sf
1953
Doug Zongker24cd2802012-08-14 16:36:15 -07001954 if self.diff_program:
1955 diff_program = self.diff_program
1956 else:
1957 ext = os.path.splitext(tf.name)[1]
1958 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001959
1960 ttemp = tf.WriteToTemp()
1961 stemp = sf.WriteToTemp()
1962
1963 ext = os.path.splitext(tf.name)[1]
1964
1965 try:
1966 ptemp = tempfile.NamedTemporaryFile()
1967 if isinstance(diff_program, list):
1968 cmd = copy.copy(diff_program)
1969 else:
1970 cmd = [diff_program]
1971 cmd.append(stemp.name)
1972 cmd.append(ttemp.name)
1973 cmd.append(ptemp.name)
1974 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001975 err = []
1976 def run():
1977 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001978 if e:
1979 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001980 th = threading.Thread(target=run)
1981 th.start()
1982 th.join(timeout=300) # 5 mins
1983 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001984 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001985 p.terminate()
1986 th.join(5)
1987 if th.is_alive():
1988 p.kill()
1989 th.join()
1990
Tianjie Xua2a9f992018-01-05 15:15:54 -08001991 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001992 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001993 self.patch = None
1994 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001995 diff = ptemp.read()
1996 finally:
1997 ptemp.close()
1998 stemp.close()
1999 ttemp.close()
2000
2001 self.patch = diff
2002 return self.tf, self.sf, self.patch
2003
2004
2005 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002006 """Returns a tuple of (target_file, source_file, patch_data).
2007
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002008 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002009 computing the patch failed.
2010 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002011 return self.tf, self.sf, self.patch
2012
2013
2014def ComputeDifferences(diffs):
2015 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002016 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002017
2018 # Do the largest files first, to try and reduce the long-pole effect.
2019 by_size = [(i.tf.size, i) for i in diffs]
2020 by_size.sort(reverse=True)
2021 by_size = [i[1] for i in by_size]
2022
2023 lock = threading.Lock()
2024 diff_iter = iter(by_size) # accessed under lock
2025
2026 def worker():
2027 try:
2028 lock.acquire()
2029 for d in diff_iter:
2030 lock.release()
2031 start = time.time()
2032 d.ComputePatch()
2033 dur = time.time() - start
2034 lock.acquire()
2035
2036 tf, sf, patch = d.GetPatch()
2037 if sf.name == tf.name:
2038 name = tf.name
2039 else:
2040 name = "%s (%s)" % (tf.name, sf.name)
2041 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002042 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002043 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002044 logger.info(
2045 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2046 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002047 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002048 except Exception:
2049 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002050 raise
2051
2052 # start worker threads; wait for them all to finish.
2053 threads = [threading.Thread(target=worker)
2054 for i in range(OPTIONS.worker_threads)]
2055 for th in threads:
2056 th.start()
2057 while threads:
2058 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002059
2060
Dan Albert8b72aef2015-03-23 19:13:21 -07002061class BlockDifference(object):
2062 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002063 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002064 self.tgt = tgt
2065 self.src = src
2066 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002067 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002068 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002069
Tao Baodd2a5892015-03-12 12:32:37 -07002070 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002071 version = max(
2072 int(i) for i in
2073 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002074 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002075 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002076
Tianjie Xu41976c72019-07-03 13:57:01 -07002077 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2078 version=self.version,
2079 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002080 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002081 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002082 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002083 self.touched_src_ranges = b.touched_src_ranges
2084 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002085
Yifan Hong10c530d2018-12-27 17:34:18 -08002086 # On devices with dynamic partitions, for new partitions,
2087 # src is None but OPTIONS.source_info_dict is not.
2088 if OPTIONS.source_info_dict is None:
2089 is_dynamic_build = OPTIONS.info_dict.get(
2090 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002091 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002092 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002093 is_dynamic_build = OPTIONS.source_info_dict.get(
2094 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002095 is_dynamic_source = partition in shlex.split(
2096 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002097
Yifan Hongbb2658d2019-01-25 12:30:58 -08002098 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002099 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2100
Yifan Hongbb2658d2019-01-25 12:30:58 -08002101 # For dynamic partitions builds, check partition list in both source
2102 # and target build because new partitions may be added, and existing
2103 # partitions may be removed.
2104 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2105
Yifan Hong10c530d2018-12-27 17:34:18 -08002106 if is_dynamic:
2107 self.device = 'map_partition("%s")' % partition
2108 else:
2109 if OPTIONS.source_info_dict is None:
2110 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2111 else:
2112 _, device_path = GetTypeAndDevice("/" + partition,
2113 OPTIONS.source_info_dict)
2114 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002115
Tao Baod8d14be2016-02-04 14:26:02 -08002116 @property
2117 def required_cache(self):
2118 return self._required_cache
2119
Tao Bao76def242017-11-21 09:25:31 -08002120 def WriteScript(self, script, output_zip, progress=None,
2121 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002122 if not self.src:
2123 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002124 script.Print("Patching %s image unconditionally..." % (self.partition,))
2125 else:
2126 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002127
Dan Albert8b72aef2015-03-23 19:13:21 -07002128 if progress:
2129 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002130 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002131
2132 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002133 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002134
Tao Bao9bc6bb22015-11-09 16:58:28 -08002135 def WriteStrictVerifyScript(self, script):
2136 """Verify all the blocks in the care_map, including clobbered blocks.
2137
2138 This differs from the WriteVerifyScript() function: a) it prints different
2139 error messages; b) it doesn't allow half-way updated images to pass the
2140 verification."""
2141
2142 partition = self.partition
2143 script.Print("Verifying %s..." % (partition,))
2144 ranges = self.tgt.care_map
2145 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002146 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002147 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2148 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002149 self.device, ranges_str,
2150 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002151 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002152 script.AppendExtra("")
2153
Tao Baod522bdc2016-04-12 15:53:16 -07002154 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002155 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002156
2157 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002158 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002159 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002160
2161 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002162 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002163 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002164 ranges = self.touched_src_ranges
2165 expected_sha1 = self.touched_src_sha1
2166 else:
2167 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2168 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002169
2170 # No blocks to be checked, skipping.
2171 if not ranges:
2172 return
2173
Tao Bao5ece99d2015-05-12 11:42:31 -07002174 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002175 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002176 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002177 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2178 '"%s.patch.dat")) then' % (
2179 self.device, ranges_str, expected_sha1,
2180 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002181 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002182 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002183
Tianjie Xufc3422a2015-12-15 11:53:59 -08002184 if self.version >= 4:
2185
2186 # Bug: 21124327
2187 # When generating incrementals for the system and vendor partitions in
2188 # version 4 or newer, explicitly check the first block (which contains
2189 # the superblock) of the partition to see if it's what we expect. If
2190 # this check fails, give an explicit log message about the partition
2191 # having been remounted R/W (the most likely explanation).
2192 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002193 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002194
2195 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002196 if partition == "system":
2197 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2198 else:
2199 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002200 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002201 'ifelse (block_image_recover({device}, "{ranges}") && '
2202 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002203 'package_extract_file("{partition}.transfer.list"), '
2204 '"{partition}.new.dat", "{partition}.patch.dat"), '
2205 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002206 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002207 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002208 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002209
Tao Baodd2a5892015-03-12 12:32:37 -07002210 # Abort the OTA update. Note that the incremental OTA cannot be applied
2211 # even if it may match the checksum of the target partition.
2212 # a) If version < 3, operations like move and erase will make changes
2213 # unconditionally and damage the partition.
2214 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002215 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002216 if partition == "system":
2217 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2218 else:
2219 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2220 script.AppendExtra((
2221 'abort("E%d: %s partition has unexpected contents");\n'
2222 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002223
Yifan Hong10c530d2018-12-27 17:34:18 -08002224 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002225 partition = self.partition
2226 script.Print('Verifying the updated %s image...' % (partition,))
2227 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2228 ranges = self.tgt.care_map
2229 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002230 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002231 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002232 self.device, ranges_str,
2233 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002234
2235 # Bug: 20881595
2236 # Verify that extended blocks are really zeroed out.
2237 if self.tgt.extended:
2238 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002239 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002240 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002241 self.device, ranges_str,
2242 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002243 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002244 if partition == "system":
2245 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2246 else:
2247 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002248 script.AppendExtra(
2249 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002250 ' abort("E%d: %s partition has unexpected non-zero contents after '
2251 'OTA update");\n'
2252 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002253 else:
2254 script.Print('Verified the updated %s image.' % (partition,))
2255
Tianjie Xu209db462016-05-24 17:34:52 -07002256 if partition == "system":
2257 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2258 else:
2259 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2260
Tao Bao5fcaaef2015-06-01 13:40:49 -07002261 script.AppendExtra(
2262 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002263 ' abort("E%d: %s partition has unexpected contents after OTA '
2264 'update");\n'
2265 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002266
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002267 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002268 ZipWrite(output_zip,
2269 '{}.transfer.list'.format(self.path),
2270 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002271
Tao Bao76def242017-11-21 09:25:31 -08002272 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2273 # its size. Quailty 9 almost triples the compression time but doesn't
2274 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002275 # zip | brotli(quality 6) | brotli(quality 9)
2276 # compressed_size: 942M | 869M (~8% reduced) | 854M
2277 # compression_time: 75s | 265s | 719s
2278 # decompression_time: 15s | 25s | 25s
2279
2280 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002281 brotli_cmd = ['brotli', '--quality=6',
2282 '--output={}.new.dat.br'.format(self.path),
2283 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002284 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002285 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002286
2287 new_data_name = '{}.new.dat.br'.format(self.partition)
2288 ZipWrite(output_zip,
2289 '{}.new.dat.br'.format(self.path),
2290 new_data_name,
2291 compress_type=zipfile.ZIP_STORED)
2292 else:
2293 new_data_name = '{}.new.dat'.format(self.partition)
2294 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2295
Dan Albert8e0178d2015-01-27 15:53:15 -08002296 ZipWrite(output_zip,
2297 '{}.patch.dat'.format(self.path),
2298 '{}.patch.dat'.format(self.partition),
2299 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002300
Tianjie Xu209db462016-05-24 17:34:52 -07002301 if self.partition == "system":
2302 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2303 else:
2304 code = ErrorCode.VENDOR_UPDATE_FAILURE
2305
Yifan Hong10c530d2018-12-27 17:34:18 -08002306 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002307 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002308 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002309 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002310 device=self.device, partition=self.partition,
2311 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002312 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002313
Dan Albert8b72aef2015-03-23 19:13:21 -07002314 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002315 data = source.ReadRangeSet(ranges)
2316 ctx = sha1()
2317
2318 for p in data:
2319 ctx.update(p)
2320
2321 return ctx.hexdigest()
2322
Tao Baoe9b61912015-07-09 17:37:49 -07002323 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2324 """Return the hash value for all zero blocks."""
2325 zero_block = '\x00' * 4096
2326 ctx = sha1()
2327 for _ in range(num_blocks):
2328 ctx.update(zero_block)
2329
2330 return ctx.hexdigest()
2331
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002332
Tianjie Xu41976c72019-07-03 13:57:01 -07002333# Expose these two classes to support vendor-specific scripts
2334DataImage = images.DataImage
2335EmptyImage = images.EmptyImage
2336
Tao Bao76def242017-11-21 09:25:31 -08002337
Doug Zongker96a57e72010-09-26 14:57:41 -07002338# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002339PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002340 "ext4": "EMMC",
2341 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002342 "f2fs": "EMMC",
2343 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002344}
Doug Zongker96a57e72010-09-26 14:57:41 -07002345
Tao Bao76def242017-11-21 09:25:31 -08002346
Doug Zongker96a57e72010-09-26 14:57:41 -07002347def GetTypeAndDevice(mount_point, info):
2348 fstab = info["fstab"]
2349 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002350 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2351 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002352 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002353 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002354
2355
2356def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002357 """Parses and converts a PEM-encoded certificate into DER-encoded.
2358
2359 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2360
2361 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002362 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002363 """
2364 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002365 save = False
2366 for line in data.split("\n"):
2367 if "--END CERTIFICATE--" in line:
2368 break
2369 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002370 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002371 if "--BEGIN CERTIFICATE--" in line:
2372 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002373 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002374 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002375
Tao Bao04e1f012018-02-04 12:13:35 -08002376
2377def ExtractPublicKey(cert):
2378 """Extracts the public key (PEM-encoded) from the given certificate file.
2379
2380 Args:
2381 cert: The certificate filename.
2382
2383 Returns:
2384 The public key string.
2385
2386 Raises:
2387 AssertionError: On non-zero return from 'openssl'.
2388 """
2389 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2390 # While openssl 1.1 writes the key into the given filename followed by '-out',
2391 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2392 # stdout instead.
2393 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2394 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2395 pubkey, stderrdata = proc.communicate()
2396 assert proc.returncode == 0, \
2397 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2398 return pubkey
2399
2400
Tao Bao1ac886e2019-06-26 11:58:22 -07002401def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002402 """Extracts the AVB public key from the given public or private key.
2403
2404 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002405 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002406 key: The input key file, which should be PEM-encoded public or private key.
2407
2408 Returns:
2409 The path to the extracted AVB public key file.
2410 """
2411 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2412 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002413 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002414 return output
2415
2416
Doug Zongker412c02f2014-02-13 10:58:24 -08002417def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2418 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002419 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002420
Tao Bao6d5d6232018-03-09 17:04:42 -08002421 Most of the space in the boot and recovery images is just the kernel, which is
2422 identical for the two, so the resulting patch should be efficient. Add it to
2423 the output zip, along with a shell script that is run from init.rc on first
2424 boot to actually do the patching and install the new recovery image.
2425
2426 Args:
2427 input_dir: The top-level input directory of the target-files.zip.
2428 output_sink: The callback function that writes the result.
2429 recovery_img: File object for the recovery image.
2430 boot_img: File objects for the boot image.
2431 info_dict: A dict returned by common.LoadInfoDict() on the input
2432 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002433 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002434 if info_dict is None:
2435 info_dict = OPTIONS.info_dict
2436
Tao Bao6d5d6232018-03-09 17:04:42 -08002437 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002438
Tao Baof2cffbd2015-07-22 12:33:18 -07002439 if full_recovery_image:
2440 output_sink("etc/recovery.img", recovery_img.data)
2441
2442 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002443 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002444 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002445 # With system-root-image, boot and recovery images will have mismatching
2446 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2447 # to handle such a case.
2448 if system_root_image:
2449 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002450 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002451 assert not os.path.exists(path)
2452 else:
2453 diff_program = ["imgdiff"]
2454 if os.path.exists(path):
2455 diff_program.append("-b")
2456 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002457 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002458 else:
2459 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002460
2461 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2462 _, _, patch = d.ComputePatch()
2463 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002464
Dan Albertebb19aa2015-03-27 19:11:53 -07002465 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002466 # The following GetTypeAndDevice()s need to use the path in the target
2467 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002468 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2469 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2470 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002471 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002472
Tao Baof2cffbd2015-07-22 12:33:18 -07002473 if full_recovery_image:
2474 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002475if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2476 applypatch \\
2477 --flash /system/etc/recovery.img \\
2478 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2479 log -t recovery "Installing new recovery image: succeeded" || \\
2480 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002481else
2482 log -t recovery "Recovery image already installed"
2483fi
2484""" % {'type': recovery_type,
2485 'device': recovery_device,
2486 'sha1': recovery_img.sha1,
2487 'size': recovery_img.size}
2488 else:
2489 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002490if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2491 applypatch %(bonus_args)s \\
2492 --patch /system/recovery-from-boot.p \\
2493 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2494 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2495 log -t recovery "Installing new recovery image: succeeded" || \\
2496 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002497else
2498 log -t recovery "Recovery image already installed"
2499fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002500""" % {'boot_size': boot_img.size,
2501 'boot_sha1': boot_img.sha1,
2502 'recovery_size': recovery_img.size,
2503 'recovery_sha1': recovery_img.sha1,
2504 'boot_type': boot_type,
2505 'boot_device': boot_device,
2506 'recovery_type': recovery_type,
2507 'recovery_device': recovery_device,
2508 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002509
2510 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002511 # in the L release.
2512 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002513
Tao Bao32fcdab2018-10-12 10:30:39 -07002514 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002515
Tao Baoda30cfa2017-12-01 16:19:46 -08002516 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002517
2518
2519class DynamicPartitionUpdate(object):
2520 def __init__(self, src_group=None, tgt_group=None, progress=None,
2521 block_difference=None):
2522 self.src_group = src_group
2523 self.tgt_group = tgt_group
2524 self.progress = progress
2525 self.block_difference = block_difference
2526
2527 @property
2528 def src_size(self):
2529 if not self.block_difference:
2530 return 0
2531 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2532
2533 @property
2534 def tgt_size(self):
2535 if not self.block_difference:
2536 return 0
2537 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2538
2539 @staticmethod
2540 def _GetSparseImageSize(img):
2541 if not img:
2542 return 0
2543 return img.blocksize * img.total_blocks
2544
2545
2546class DynamicGroupUpdate(object):
2547 def __init__(self, src_size=None, tgt_size=None):
2548 # None: group does not exist. 0: no size limits.
2549 self.src_size = src_size
2550 self.tgt_size = tgt_size
2551
2552
2553class DynamicPartitionsDifference(object):
2554 def __init__(self, info_dict, block_diffs, progress_dict=None,
2555 source_info_dict=None):
2556 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002557 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002558
2559 self._remove_all_before_apply = False
2560 if source_info_dict is None:
2561 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002562 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002563
Tao Baof1113e92019-06-18 12:10:14 -07002564 block_diff_dict = collections.OrderedDict(
2565 [(e.partition, e) for e in block_diffs])
2566
Yifan Hong10c530d2018-12-27 17:34:18 -08002567 assert len(block_diff_dict) == len(block_diffs), \
2568 "Duplicated BlockDifference object for {}".format(
2569 [partition for partition, count in
2570 collections.Counter(e.partition for e in block_diffs).items()
2571 if count > 1])
2572
Yifan Hong79997e52019-01-23 16:56:19 -08002573 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002574
2575 for p, block_diff in block_diff_dict.items():
2576 self._partition_updates[p] = DynamicPartitionUpdate()
2577 self._partition_updates[p].block_difference = block_diff
2578
2579 for p, progress in progress_dict.items():
2580 if p in self._partition_updates:
2581 self._partition_updates[p].progress = progress
2582
2583 tgt_groups = shlex.split(info_dict.get(
2584 "super_partition_groups", "").strip())
2585 src_groups = shlex.split(source_info_dict.get(
2586 "super_partition_groups", "").strip())
2587
2588 for g in tgt_groups:
2589 for p in shlex.split(info_dict.get(
2590 "super_%s_partition_list" % g, "").strip()):
2591 assert p in self._partition_updates, \
2592 "{} is in target super_{}_partition_list but no BlockDifference " \
2593 "object is provided.".format(p, g)
2594 self._partition_updates[p].tgt_group = g
2595
2596 for g in src_groups:
2597 for p in shlex.split(source_info_dict.get(
2598 "super_%s_partition_list" % g, "").strip()):
2599 assert p in self._partition_updates, \
2600 "{} is in source super_{}_partition_list but no BlockDifference " \
2601 "object is provided.".format(p, g)
2602 self._partition_updates[p].src_group = g
2603
Yifan Hong45433e42019-01-18 13:55:25 -08002604 target_dynamic_partitions = set(shlex.split(info_dict.get(
2605 "dynamic_partition_list", "").strip()))
2606 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2607 if u.tgt_size)
2608 assert block_diffs_with_target == target_dynamic_partitions, \
2609 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2610 list(target_dynamic_partitions), list(block_diffs_with_target))
2611
2612 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2613 "dynamic_partition_list", "").strip()))
2614 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2615 if u.src_size)
2616 assert block_diffs_with_source == source_dynamic_partitions, \
2617 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2618 list(source_dynamic_partitions), list(block_diffs_with_source))
2619
Yifan Hong10c530d2018-12-27 17:34:18 -08002620 if self._partition_updates:
2621 logger.info("Updating dynamic partitions %s",
2622 self._partition_updates.keys())
2623
Yifan Hong79997e52019-01-23 16:56:19 -08002624 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002625
2626 for g in tgt_groups:
2627 self._group_updates[g] = DynamicGroupUpdate()
2628 self._group_updates[g].tgt_size = int(info_dict.get(
2629 "super_%s_group_size" % g, "0").strip())
2630
2631 for g in src_groups:
2632 if g not in self._group_updates:
2633 self._group_updates[g] = DynamicGroupUpdate()
2634 self._group_updates[g].src_size = int(source_info_dict.get(
2635 "super_%s_group_size" % g, "0").strip())
2636
2637 self._Compute()
2638
2639 def WriteScript(self, script, output_zip, write_verify_script=False):
2640 script.Comment('--- Start patching dynamic partitions ---')
2641 for p, u in self._partition_updates.items():
2642 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2643 script.Comment('Patch partition %s' % p)
2644 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2645 write_verify_script=False)
2646
2647 op_list_path = MakeTempFile()
2648 with open(op_list_path, 'w') as f:
2649 for line in self._op_list:
2650 f.write('{}\n'.format(line))
2651
2652 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2653
2654 script.Comment('Update dynamic partition metadata')
2655 script.AppendExtra('assert(update_dynamic_partitions('
2656 'package_extract_file("dynamic_partitions_op_list")));')
2657
2658 if write_verify_script:
2659 for p, u in self._partition_updates.items():
2660 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2661 u.block_difference.WritePostInstallVerifyScript(script)
2662 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2663
2664 for p, u in self._partition_updates.items():
2665 if u.tgt_size and u.src_size <= u.tgt_size:
2666 script.Comment('Patch partition %s' % p)
2667 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2668 write_verify_script=write_verify_script)
2669 if write_verify_script:
2670 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2671
2672 script.Comment('--- End patching dynamic partitions ---')
2673
2674 def _Compute(self):
2675 self._op_list = list()
2676
2677 def append(line):
2678 self._op_list.append(line)
2679
2680 def comment(line):
2681 self._op_list.append("# %s" % line)
2682
2683 if self._remove_all_before_apply:
2684 comment('Remove all existing dynamic partitions and groups before '
2685 'applying full OTA')
2686 append('remove_all_groups')
2687
2688 for p, u in self._partition_updates.items():
2689 if u.src_group and not u.tgt_group:
2690 append('remove %s' % p)
2691
2692 for p, u in self._partition_updates.items():
2693 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2694 comment('Move partition %s from %s to default' % (p, u.src_group))
2695 append('move %s default' % p)
2696
2697 for p, u in self._partition_updates.items():
2698 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2699 comment('Shrink partition %s from %d to %d' %
2700 (p, u.src_size, u.tgt_size))
2701 append('resize %s %s' % (p, u.tgt_size))
2702
2703 for g, u in self._group_updates.items():
2704 if u.src_size is not None and u.tgt_size is None:
2705 append('remove_group %s' % g)
2706 if (u.src_size is not None and u.tgt_size is not None and
2707 u.src_size > u.tgt_size):
2708 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2709 append('resize_group %s %d' % (g, u.tgt_size))
2710
2711 for g, u in self._group_updates.items():
2712 if u.src_size is None and u.tgt_size is not None:
2713 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2714 append('add_group %s %d' % (g, u.tgt_size))
2715 if (u.src_size is not None and u.tgt_size is not None and
2716 u.src_size < u.tgt_size):
2717 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2718 append('resize_group %s %d' % (g, u.tgt_size))
2719
2720 for p, u in self._partition_updates.items():
2721 if u.tgt_group and not u.src_group:
2722 comment('Add partition %s to group %s' % (p, u.tgt_group))
2723 append('add %s %s' % (p, u.tgt_group))
2724
2725 for p, u in self._partition_updates.items():
2726 if u.tgt_size and u.src_size < u.tgt_size:
2727 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2728 append('resize %s %d' % (p, u.tgt_size))
2729
2730 for p, u in self._partition_updates.items():
2731 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2732 comment('Move partition %s from default to %s' %
2733 (p, u.tgt_group))
2734 append('move %s %s' % (p, u.tgt_group))