blob: 2401e469cc361cf9e65a00373a6868f2ac18ad22 [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")
619 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
620 if key_path and algorithm:
621 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700622 avb_salt = OPTIONS.info_dict.get("avb_salt")
623 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700624 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700625 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800626
627
Daniel Norman276f0622019-07-26 14:13:51 -0700628def GetAvbPartitionArg(partition, image, info_dict = None):
629 """Returns the VBMeta arguments for partition.
630
631 It sets up the VBMeta argument by including the partition descriptor from the
632 given 'image', or by configuring the partition as a chained partition.
633
634 Args:
635 partition: The name of the partition (e.g. "system").
636 image: The path to the partition image.
637 info_dict: A dict returned by common.LoadInfoDict(). Will use
638 OPTIONS.info_dict if None has been given.
639
640 Returns:
641 A list of VBMeta arguments.
642 """
643 if info_dict is None:
644 info_dict = OPTIONS.info_dict
645
646 # Check if chain partition is used.
647 key_path = info_dict.get("avb_" + partition + "_key_path")
648 if key_path:
649 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
650 return ["--chain_partition", chained_partition_arg]
651 else:
652 return ["--include_descriptors_from_image", image]
653
654
Tao Bao02a08592018-07-22 12:40:45 -0700655def GetAvbChainedPartitionArg(partition, info_dict, key=None):
656 """Constructs and returns the arg to build or verify a chained partition.
657
658 Args:
659 partition: The partition name.
660 info_dict: The info dict to look up the key info and rollback index
661 location.
662 key: The key to be used for building or verifying the partition. Defaults to
663 the key listed in info_dict.
664
665 Returns:
666 A string of form "partition:rollback_index_location:key" that can be used to
667 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700668 """
669 if key is None:
670 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao1ac886e2019-06-26 11:58:22 -0700671 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700672 rollback_index_location = info_dict[
673 "avb_" + partition + "_rollback_index_location"]
674 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
675
676
Daniel Norman276f0622019-07-26 14:13:51 -0700677def BuildVBMeta(image_path, partitions, name, needed_partitions):
678 """Creates a VBMeta image.
679
680 It generates the requested VBMeta image. The requested image could be for
681 top-level or chained VBMeta image, which is determined based on the name.
682
683 Args:
684 image_path: The output path for the new VBMeta image.
685 partitions: A dict that's keyed by partition names with image paths as
686 values. Only valid partition names are accepted, as listed in
687 common.AVB_PARTITIONS.
688 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
689 needed_partitions: Partitions whose descriptors should be included into the
690 generated VBMeta image.
691
692 Raises:
693 AssertionError: On invalid input args.
694 """
695 avbtool = OPTIONS.info_dict["avb_avbtool"]
696 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
697 AppendAVBSigningArgs(cmd, name)
698
699 for partition, path in partitions.items():
700 if partition not in needed_partitions:
701 continue
702 assert (partition in AVB_PARTITIONS or
703 partition in AVB_VBMETA_PARTITIONS), \
704 'Unknown partition: {}'.format(partition)
705 assert os.path.exists(path), \
706 'Failed to find {} for {}'.format(path, partition)
707 cmd.extend(GetAvbPartitionArg(partition, path))
708
709 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
710 if args and args.strip():
711 split_args = shlex.split(args)
712 for index, arg in enumerate(split_args[:-1]):
713 # Sanity check that the image file exists. Some images might be defined
714 # as a path relative to source tree, which may not be available at the
715 # same location when running this script (we have the input target_files
716 # zip only). For such cases, we additionally scan other locations (e.g.
717 # IMAGES/, RADIO/, etc) before bailing out.
718 if arg == '--include_descriptors_from_image':
719 image_path = split_args[index + 1]
720 if os.path.exists(image_path):
721 continue
722 found = False
723 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
724 alt_path = os.path.join(
725 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
726 if os.path.exists(alt_path):
727 split_args[index + 1] = alt_path
728 found = True
729 break
730 assert found, 'Failed to find {}'.format(image_path)
731 cmd.extend(split_args)
732
733 RunAndCheckOutput(cmd)
734
735
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700736def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800737 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700738 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700739
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700740 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800741 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
742 we are building a two-step special image (i.e. building a recovery image to
743 be loaded into /boot in two-step OTAs).
744
745 Return the image data, or None if sourcedir does not appear to contains files
746 for building the requested image.
747 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700748
749 def make_ramdisk():
750 ramdisk_img = tempfile.NamedTemporaryFile()
751
752 if os.access(fs_config_file, os.F_OK):
753 cmd = ["mkbootfs", "-f", fs_config_file,
754 os.path.join(sourcedir, "RAMDISK")]
755 else:
756 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
757 p1 = Run(cmd, stdout=subprocess.PIPE)
758 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
759
760 p2.wait()
761 p1.wait()
762 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
763 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
764
765 return ramdisk_img
766
767 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
768 return None
769
770 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700771 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700772
Doug Zongkerd5131602012-08-02 14:46:42 -0700773 if info_dict is None:
774 info_dict = OPTIONS.info_dict
775
Doug Zongkereef39442009-04-02 12:14:19 -0700776 img = tempfile.NamedTemporaryFile()
777
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700778 if has_ramdisk:
779 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700780
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800781 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
782 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
783
784 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700785
Benoit Fradina45a8682014-07-14 21:00:43 +0200786 fn = os.path.join(sourcedir, "second")
787 if os.access(fn, os.F_OK):
788 cmd.append("--second")
789 cmd.append(fn)
790
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800791 fn = os.path.join(sourcedir, "dtb")
792 if os.access(fn, os.F_OK):
793 cmd.append("--dtb")
794 cmd.append(fn)
795
Doug Zongker171f1cd2009-06-15 22:36:37 -0700796 fn = os.path.join(sourcedir, "cmdline")
797 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700798 cmd.append("--cmdline")
799 cmd.append(open(fn).read().rstrip("\n"))
800
801 fn = os.path.join(sourcedir, "base")
802 if os.access(fn, os.F_OK):
803 cmd.append("--base")
804 cmd.append(open(fn).read().rstrip("\n"))
805
Ying Wang4de6b5b2010-08-25 14:29:34 -0700806 fn = os.path.join(sourcedir, "pagesize")
807 if os.access(fn, os.F_OK):
808 cmd.append("--pagesize")
809 cmd.append(open(fn).read().rstrip("\n"))
810
Tao Bao76def242017-11-21 09:25:31 -0800811 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700812 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700813 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700814
Tao Bao76def242017-11-21 09:25:31 -0800815 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000816 if args and args.strip():
817 cmd.extend(shlex.split(args))
818
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700819 if has_ramdisk:
820 cmd.extend(["--ramdisk", ramdisk_img.name])
821
Tao Baod95e9fd2015-03-29 23:07:41 -0700822 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800823 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700824 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700825 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700826 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700827 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700828
Tao Baobf70c312017-07-11 17:27:55 -0700829 # "boot" or "recovery", without extension.
830 partition_name = os.path.basename(sourcedir).lower()
831
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800832 if partition_name == "recovery":
833 if info_dict.get("include_recovery_dtbo") == "true":
834 fn = os.path.join(sourcedir, "recovery_dtbo")
835 cmd.extend(["--recovery_dtbo", fn])
836 if info_dict.get("include_recovery_acpio") == "true":
837 fn = os.path.join(sourcedir, "recovery_acpio")
838 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700839
Tao Bao986ee862018-10-04 15:46:16 -0700840 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700841
Tao Bao76def242017-11-21 09:25:31 -0800842 if (info_dict.get("boot_signer") == "true" and
843 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800844 # Hard-code the path as "/boot" for two-step special recovery image (which
845 # will be loaded into /boot during the two-step OTA).
846 if two_step_image:
847 path = "/boot"
848 else:
Tao Baobf70c312017-07-11 17:27:55 -0700849 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700850 cmd = [OPTIONS.boot_signer_path]
851 cmd.extend(OPTIONS.boot_signer_args)
852 cmd.extend([path, img.name,
853 info_dict["verity_key"] + ".pk8",
854 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700855 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700856
Tao Baod95e9fd2015-03-29 23:07:41 -0700857 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800858 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700859 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700860 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800861 # We have switched from the prebuilt futility binary to using the tool
862 # (futility-host) built from the source. Override the setting in the old
863 # TF.zip.
864 futility = info_dict["futility"]
865 if futility.startswith("prebuilts/"):
866 futility = "futility-host"
867 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700868 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700869 info_dict["vboot_key"] + ".vbprivk",
870 info_dict["vboot_subkey"] + ".vbprivk",
871 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700872 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700873 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700874
Tao Baof3282b42015-04-01 11:21:55 -0700875 # Clean up the temp files.
876 img_unsigned.close()
877 img_keyblock.close()
878
David Zeuthen8fecb282017-12-01 16:24:01 -0500879 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800880 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700881 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500882 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400883 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700884 "--partition_size", str(part_size), "--partition_name",
885 partition_name]
886 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500887 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400888 if args and args.strip():
889 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700890 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500891
892 img.seek(os.SEEK_SET, 0)
893 data = img.read()
894
895 if has_ramdisk:
896 ramdisk_img.close()
897 img.close()
898
899 return data
900
901
Doug Zongkerd5131602012-08-02 14:46:42 -0700902def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800903 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700904 """Return a File object with the desired bootable image.
905
906 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
907 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
908 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700909
Doug Zongker55d93282011-01-25 17:03:34 -0800910 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
911 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700912 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800913 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700914
915 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
916 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700917 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700918 return File.FromLocalFile(name, prebuilt_path)
919
Tao Bao32fcdab2018-10-12 10:30:39 -0700920 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700921
922 if info_dict is None:
923 info_dict = OPTIONS.info_dict
924
925 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800926 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
927 # for recovery.
928 has_ramdisk = (info_dict.get("system_root_image") != "true" or
929 prebuilt_name != "boot.img" or
930 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700931
Doug Zongker6f1d0312014-08-22 08:07:12 -0700932 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400933 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
934 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800935 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700936 if data:
937 return File(name, data)
938 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800939
Doug Zongkereef39442009-04-02 12:14:19 -0700940
Narayan Kamatha07bf042017-08-14 14:49:21 +0100941def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800942 """Gunzips the given gzip compressed file to a given output file."""
943 with gzip.open(in_filename, "rb") as in_file, \
944 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100945 shutil.copyfileobj(in_file, out_file)
946
947
Tao Bao0ff15de2019-03-20 11:26:06 -0700948def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800949 """Unzips the archive to the given directory.
950
951 Args:
952 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800953 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700954 patterns: Files to unzip from the archive. If omitted, will unzip the entire
955 archvie. Non-matching patterns will be filtered out. If there's no match
956 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800957 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800958 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700959 if patterns is not None:
960 # Filter out non-matching patterns. unzip will complain otherwise.
961 with zipfile.ZipFile(filename) as input_zip:
962 names = input_zip.namelist()
963 filtered = [
964 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
965
966 # There isn't any matching files. Don't unzip anything.
967 if not filtered:
968 return
969 cmd.extend(filtered)
970
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800971 RunAndCheckOutput(cmd)
972
973
Doug Zongker75f17362009-12-08 13:46:44 -0800974def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800975 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800976
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800977 Args:
978 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
979 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
980
981 pattern: Files to unzip from the archive. If omitted, will unzip the entire
982 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800983
Tao Bao1c830bf2017-12-25 10:43:47 -0800984 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800985 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800986 """
Doug Zongkereef39442009-04-02 12:14:19 -0700987
Tao Bao1c830bf2017-12-25 10:43:47 -0800988 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800989 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
990 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800991 UnzipToDir(m.group(1), tmp, pattern)
992 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800993 filename = m.group(1)
994 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800995 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800996
Tao Baodba59ee2018-01-09 13:21:02 -0800997 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700998
999
Yifan Hong8a66a712019-04-04 15:37:57 -07001000def GetUserImage(which, tmpdir, input_zip,
1001 info_dict=None,
1002 allow_shared_blocks=None,
1003 hashtree_info_generator=None,
1004 reset_file_map=False):
1005 """Returns an Image object suitable for passing to BlockImageDiff.
1006
1007 This function loads the specified image from the given path. If the specified
1008 image is sparse, it also performs additional processing for OTA purpose. For
1009 example, it always adds block 0 to clobbered blocks list. It also detects
1010 files that cannot be reconstructed from the block list, for whom we should
1011 avoid applying imgdiff.
1012
1013 Args:
1014 which: The partition name.
1015 tmpdir: The directory that contains the prebuilt image and block map file.
1016 input_zip: The target-files ZIP archive.
1017 info_dict: The dict to be looked up for relevant info.
1018 allow_shared_blocks: If image is sparse, whether having shared blocks is
1019 allowed. If none, it is looked up from info_dict.
1020 hashtree_info_generator: If present and image is sparse, generates the
1021 hashtree_info for this sparse image.
1022 reset_file_map: If true and image is sparse, reset file map before returning
1023 the image.
1024 Returns:
1025 A Image object. If it is a sparse image and reset_file_map is False, the
1026 image will have file_map info loaded.
1027 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001028 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001029 info_dict = LoadInfoDict(input_zip)
1030
1031 is_sparse = info_dict.get("extfs_sparse_flag")
1032
1033 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1034 # shared blocks (i.e. some blocks will show up in multiple files' block
1035 # list). We can only allocate such shared blocks to the first "owner", and
1036 # disable imgdiff for all later occurrences.
1037 if allow_shared_blocks is None:
1038 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1039
1040 if is_sparse:
1041 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1042 hashtree_info_generator)
1043 if reset_file_map:
1044 img.ResetFileMap()
1045 return img
1046 else:
1047 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1048
1049
1050def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1051 """Returns a Image object suitable for passing to BlockImageDiff.
1052
1053 This function loads the specified non-sparse image from the given path.
1054
1055 Args:
1056 which: The partition name.
1057 tmpdir: The directory that contains the prebuilt image and block map file.
1058 Returns:
1059 A Image object.
1060 """
1061 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1062 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1063
1064 # The image and map files must have been created prior to calling
1065 # ota_from_target_files.py (since LMP).
1066 assert os.path.exists(path) and os.path.exists(mappath)
1067
Tianjie Xu41976c72019-07-03 13:57:01 -07001068 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1069
Yifan Hong8a66a712019-04-04 15:37:57 -07001070
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001071def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1072 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001073 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1074
1075 This function loads the specified sparse image from the given path, and
1076 performs additional processing for OTA purpose. For example, it always adds
1077 block 0 to clobbered blocks list. It also detects files that cannot be
1078 reconstructed from the block list, for whom we should avoid applying imgdiff.
1079
1080 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001081 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001082 tmpdir: The directory that contains the prebuilt image and block map file.
1083 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001084 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001085 hashtree_info_generator: If present, generates the hashtree_info for this
1086 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001087 Returns:
1088 A SparseImage object, with file_map info loaded.
1089 """
Tao Baoc765cca2018-01-31 17:32:40 -08001090 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1091 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1092
1093 # The image and map files must have been created prior to calling
1094 # ota_from_target_files.py (since LMP).
1095 assert os.path.exists(path) and os.path.exists(mappath)
1096
1097 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1098 # it to clobbered_blocks so that it will be written to the target
1099 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1100 clobbered_blocks = "0"
1101
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001102 image = sparse_img.SparseImage(
1103 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1104 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001105
1106 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1107 # if they contain all zeros. We can't reconstruct such a file from its block
1108 # list. Tag such entries accordingly. (Bug: 65213616)
1109 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001110 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001111 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001112 continue
1113
Tom Cherryd14b8952018-08-09 14:26:00 -07001114 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1115 # filename listed in system.map may contain an additional leading slash
1116 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1117 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001118 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001119
Tom Cherryd14b8952018-08-09 14:26:00 -07001120 # Special handling another case, where files not under /system
1121 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001122 if which == 'system' and not arcname.startswith('SYSTEM'):
1123 arcname = 'ROOT/' + arcname
1124
1125 assert arcname in input_zip.namelist(), \
1126 "Failed to find the ZIP entry for {}".format(entry)
1127
Tao Baoc765cca2018-01-31 17:32:40 -08001128 info = input_zip.getinfo(arcname)
1129 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001130
1131 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001132 # image, check the original block list to determine its completeness. Note
1133 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001134 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001135 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001136
Tao Baoc765cca2018-01-31 17:32:40 -08001137 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1138 ranges.extra['incomplete'] = True
1139
1140 return image
1141
1142
Doug Zongkereef39442009-04-02 12:14:19 -07001143def GetKeyPasswords(keylist):
1144 """Given a list of keys, prompt the user to enter passwords for
1145 those which require them. Return a {key: password} dict. password
1146 will be None if the key has no password."""
1147
Doug Zongker8ce7c252009-05-22 13:34:54 -07001148 no_passwords = []
1149 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001150 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001151 devnull = open("/dev/null", "w+b")
1152 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001153 # We don't need a password for things that aren't really keys.
1154 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001155 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001156 continue
1157
T.R. Fullhart37e10522013-03-18 10:31:26 -07001158 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001159 "-inform", "DER", "-nocrypt"],
1160 stdin=devnull.fileno(),
1161 stdout=devnull.fileno(),
1162 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001163 p.communicate()
1164 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001165 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001166 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001167 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001168 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1169 "-inform", "DER", "-passin", "pass:"],
1170 stdin=devnull.fileno(),
1171 stdout=devnull.fileno(),
1172 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001173 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001174 if p.returncode == 0:
1175 # Encrypted key with empty string as password.
1176 key_passwords[k] = ''
1177 elif stderr.startswith('Error decrypting key'):
1178 # Definitely encrypted key.
1179 # It would have said "Error reading key" if it didn't parse correctly.
1180 need_passwords.append(k)
1181 else:
1182 # Potentially, a type of key that openssl doesn't understand.
1183 # We'll let the routines in signapk.jar handle it.
1184 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001185 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001186
T.R. Fullhart37e10522013-03-18 10:31:26 -07001187 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001188 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001189 return key_passwords
1190
1191
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001192def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001193 """Gets the minSdkVersion declared in the APK.
1194
1195 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1196 This can be both a decimal number (API Level) or a codename.
1197
1198 Args:
1199 apk_name: The APK filename.
1200
1201 Returns:
1202 The parsed SDK version string.
1203
1204 Raises:
1205 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001206 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001207 proc = Run(
1208 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1209 stderr=subprocess.PIPE)
1210 stdoutdata, stderrdata = proc.communicate()
1211 if proc.returncode != 0:
1212 raise ExternalError(
1213 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1214 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001215
Tao Baof47bf0f2018-03-21 23:28:51 -07001216 for line in stdoutdata.split("\n"):
1217 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001218 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1219 if m:
1220 return m.group(1)
1221 raise ExternalError("No minSdkVersion returned by aapt")
1222
1223
1224def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001225 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001226
Tao Baof47bf0f2018-03-21 23:28:51 -07001227 If minSdkVersion is set to a codename, it is translated to a number using the
1228 provided map.
1229
1230 Args:
1231 apk_name: The APK filename.
1232
1233 Returns:
1234 The parsed SDK version number.
1235
1236 Raises:
1237 ExternalError: On failing to get the min SDK version number.
1238 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001239 version = GetMinSdkVersion(apk_name)
1240 try:
1241 return int(version)
1242 except ValueError:
1243 # Not a decimal number. Codename?
1244 if version in codename_to_api_level_map:
1245 return codename_to_api_level_map[version]
1246 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001247 raise ExternalError(
1248 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1249 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001250
1251
1252def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001253 codename_to_api_level_map=None, whole_file=False,
1254 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001255 """Sign the input_name zip/jar/apk, producing output_name. Use the
1256 given key and password (the latter may be None if the key does not
1257 have a password.
1258
Doug Zongker951495f2009-08-14 12:44:19 -07001259 If whole_file is true, use the "-w" option to SignApk to embed a
1260 signature that covers the whole file in the archive comment of the
1261 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001262
1263 min_api_level is the API Level (int) of the oldest platform this file may end
1264 up on. If not specified for an APK, the API Level is obtained by interpreting
1265 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1266
1267 codename_to_api_level_map is needed to translate the codename which may be
1268 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001269
1270 Caller may optionally specify extra args to be passed to SignApk, which
1271 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001272 """
Tao Bao76def242017-11-21 09:25:31 -08001273 if codename_to_api_level_map is None:
1274 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001275 if extra_signapk_args is None:
1276 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001277
Alex Klyubin9667b182015-12-10 13:38:50 -08001278 java_library_path = os.path.join(
1279 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1280
Tao Baoe95540e2016-11-08 12:08:53 -08001281 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1282 ["-Djava.library.path=" + java_library_path,
1283 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001284 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001285 if whole_file:
1286 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001287
1288 min_sdk_version = min_api_level
1289 if min_sdk_version is None:
1290 if not whole_file:
1291 min_sdk_version = GetMinSdkVersionInt(
1292 input_name, codename_to_api_level_map)
1293 if min_sdk_version is not None:
1294 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1295
T.R. Fullhart37e10522013-03-18 10:31:26 -07001296 cmd.extend([key + OPTIONS.public_key_suffix,
1297 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001298 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001299
Tao Bao73dd4f42018-10-04 16:25:33 -07001300 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001301 if password is not None:
1302 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001303 stdoutdata, _ = proc.communicate(password)
1304 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001305 raise ExternalError(
1306 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001307 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001308
Doug Zongkereef39442009-04-02 12:14:19 -07001309
Doug Zongker37974732010-09-16 17:44:38 -07001310def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001311 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001312
Tao Bao9dd909e2017-11-14 11:27:32 -08001313 For non-AVB images, raise exception if the data is too big. Print a warning
1314 if the data is nearing the maximum size.
1315
1316 For AVB images, the actual image size should be identical to the limit.
1317
1318 Args:
1319 data: A string that contains all the data for the partition.
1320 target: The partition name. The ".img" suffix is optional.
1321 info_dict: The dict to be looked up for relevant info.
1322 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001323 if target.endswith(".img"):
1324 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001325 mount_point = "/" + target
1326
Ying Wangf8824af2014-06-03 14:07:27 -07001327 fs_type = None
1328 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001329 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001330 if mount_point == "/userdata":
1331 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001332 p = info_dict["fstab"][mount_point]
1333 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001334 device = p.device
1335 if "/" in device:
1336 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001337 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001338 if not fs_type or not limit:
1339 return
Doug Zongkereef39442009-04-02 12:14:19 -07001340
Andrew Boie0f9aec82012-02-14 09:32:52 -08001341 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001342 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1343 # path.
1344 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1345 if size != limit:
1346 raise ExternalError(
1347 "Mismatching image size for %s: expected %d actual %d" % (
1348 target, limit, size))
1349 else:
1350 pct = float(size) * 100.0 / limit
1351 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1352 if pct >= 99.0:
1353 raise ExternalError(msg)
1354 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001355 logger.warning("\n WARNING: %s\n", msg)
1356 else:
1357 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001358
1359
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001360def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001361 """Parses the APK certs info from a given target-files zip.
1362
1363 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1364 tuple with the following elements: (1) a dictionary that maps packages to
1365 certs (based on the "certificate" and "private_key" attributes in the file;
1366 (2) a string representing the extension of compressed APKs in the target files
1367 (e.g ".gz", ".bro").
1368
1369 Args:
1370 tf_zip: The input target_files ZipFile (already open).
1371
1372 Returns:
1373 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1374 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1375 no compressed APKs.
1376 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001377 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001378 compressed_extension = None
1379
Tao Bao0f990332017-09-08 19:02:54 -07001380 # META/apkcerts.txt contains the info for _all_ the packages known at build
1381 # time. Filter out the ones that are not installed.
1382 installed_files = set()
1383 for name in tf_zip.namelist():
1384 basename = os.path.basename(name)
1385 if basename:
1386 installed_files.add(basename)
1387
Tao Baoda30cfa2017-12-01 16:19:46 -08001388 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001389 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001390 if not line:
1391 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001392 m = re.match(
1393 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1394 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1395 line)
1396 if not m:
1397 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001398
Tao Bao818ddf52018-01-05 11:17:34 -08001399 matches = m.groupdict()
1400 cert = matches["CERT"]
1401 privkey = matches["PRIVKEY"]
1402 name = matches["NAME"]
1403 this_compressed_extension = matches["COMPRESSED"]
1404
1405 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1406 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1407 if cert in SPECIAL_CERT_STRINGS and not privkey:
1408 certmap[name] = cert
1409 elif (cert.endswith(OPTIONS.public_key_suffix) and
1410 privkey.endswith(OPTIONS.private_key_suffix) and
1411 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1412 certmap[name] = cert[:-public_key_suffix_len]
1413 else:
1414 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1415
1416 if not this_compressed_extension:
1417 continue
1418
1419 # Only count the installed files.
1420 filename = name + '.' + this_compressed_extension
1421 if filename not in installed_files:
1422 continue
1423
1424 # Make sure that all the values in the compression map have the same
1425 # extension. We don't support multiple compression methods in the same
1426 # system image.
1427 if compressed_extension:
1428 if this_compressed_extension != compressed_extension:
1429 raise ValueError(
1430 "Multiple compressed extensions: {} vs {}".format(
1431 compressed_extension, this_compressed_extension))
1432 else:
1433 compressed_extension = this_compressed_extension
1434
1435 return (certmap,
1436 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001437
1438
Doug Zongkereef39442009-04-02 12:14:19 -07001439COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001440Global options
1441
1442 -p (--path) <dir>
1443 Prepend <dir>/bin to the list of places to search for binaries run by this
1444 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001445
Doug Zongker05d3dea2009-06-22 11:32:31 -07001446 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001447 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001448
Tao Bao30df8b42018-04-23 15:32:53 -07001449 -x (--extra) <key=value>
1450 Add a key/value pair to the 'extras' dict, which device-specific extension
1451 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001452
Doug Zongkereef39442009-04-02 12:14:19 -07001453 -v (--verbose)
1454 Show command lines being executed.
1455
1456 -h (--help)
1457 Display this usage message and exit.
1458"""
1459
1460def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001461 print(docstring.rstrip("\n"))
1462 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001463
1464
1465def ParseOptions(argv,
1466 docstring,
1467 extra_opts="", extra_long_opts=(),
1468 extra_option_handler=None):
1469 """Parse the options in argv and return any arguments that aren't
1470 flags. docstring is the calling module's docstring, to be displayed
1471 for errors and -h. extra_opts and extra_long_opts are for flags
1472 defined by the caller, which are processed by passing them to
1473 extra_option_handler."""
1474
1475 try:
1476 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001477 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001478 ["help", "verbose", "path=", "signapk_path=",
1479 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001480 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001481 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1482 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001483 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001484 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001485 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001486 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001487 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001488 sys.exit(2)
1489
Doug Zongkereef39442009-04-02 12:14:19 -07001490 for o, a in opts:
1491 if o in ("-h", "--help"):
1492 Usage(docstring)
1493 sys.exit()
1494 elif o in ("-v", "--verbose"):
1495 OPTIONS.verbose = True
1496 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001497 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001498 elif o in ("--signapk_path",):
1499 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001500 elif o in ("--signapk_shared_library_path",):
1501 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001502 elif o in ("--extra_signapk_args",):
1503 OPTIONS.extra_signapk_args = shlex.split(a)
1504 elif o in ("--java_path",):
1505 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001506 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001507 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001508 elif o in ("--public_key_suffix",):
1509 OPTIONS.public_key_suffix = a
1510 elif o in ("--private_key_suffix",):
1511 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001512 elif o in ("--boot_signer_path",):
1513 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001514 elif o in ("--boot_signer_args",):
1515 OPTIONS.boot_signer_args = shlex.split(a)
1516 elif o in ("--verity_signer_path",):
1517 OPTIONS.verity_signer_path = a
1518 elif o in ("--verity_signer_args",):
1519 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001520 elif o in ("-s", "--device_specific"):
1521 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001522 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001523 key, value = a.split("=", 1)
1524 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001525 else:
1526 if extra_option_handler is None or not extra_option_handler(o, a):
1527 assert False, "unknown option \"%s\"" % (o,)
1528
Doug Zongker85448772014-09-09 14:59:20 -07001529 if OPTIONS.search_path:
1530 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1531 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001532
1533 return args
1534
1535
Tao Bao4c851b12016-09-19 13:54:38 -07001536def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001537 """Make a temp file and add it to the list of things to be deleted
1538 when Cleanup() is called. Return the filename."""
1539 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1540 os.close(fd)
1541 OPTIONS.tempfiles.append(fn)
1542 return fn
1543
1544
Tao Bao1c830bf2017-12-25 10:43:47 -08001545def MakeTempDir(prefix='tmp', suffix=''):
1546 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1547
1548 Returns:
1549 The absolute pathname of the new directory.
1550 """
1551 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1552 OPTIONS.tempfiles.append(dir_name)
1553 return dir_name
1554
1555
Doug Zongkereef39442009-04-02 12:14:19 -07001556def Cleanup():
1557 for i in OPTIONS.tempfiles:
1558 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001559 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001560 else:
1561 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001562 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001563
1564
1565class PasswordManager(object):
1566 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001567 self.editor = os.getenv("EDITOR")
1568 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001569
1570 def GetPasswords(self, items):
1571 """Get passwords corresponding to each string in 'items',
1572 returning a dict. (The dict may have keys in addition to the
1573 values in 'items'.)
1574
1575 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1576 user edit that file to add more needed passwords. If no editor is
1577 available, or $ANDROID_PW_FILE isn't define, prompts the user
1578 interactively in the ordinary way.
1579 """
1580
1581 current = self.ReadFile()
1582
1583 first = True
1584 while True:
1585 missing = []
1586 for i in items:
1587 if i not in current or not current[i]:
1588 missing.append(i)
1589 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001590 if not missing:
1591 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001592
1593 for i in missing:
1594 current[i] = ""
1595
1596 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001597 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001598 if sys.version_info[0] >= 3:
1599 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001600 answer = raw_input("try to edit again? [y]> ").strip()
1601 if answer and answer[0] not in 'yY':
1602 raise RuntimeError("key passwords unavailable")
1603 first = False
1604
1605 current = self.UpdateAndReadFile(current)
1606
Dan Albert8b72aef2015-03-23 19:13:21 -07001607 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001608 """Prompt the user to enter a value (password) for each key in
1609 'current' whose value is fales. Returns a new dict with all the
1610 values.
1611 """
1612 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001613 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001614 if v:
1615 result[k] = v
1616 else:
1617 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001618 result[k] = getpass.getpass(
1619 "Enter password for %s key> " % k).strip()
1620 if result[k]:
1621 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001622 return result
1623
1624 def UpdateAndReadFile(self, current):
1625 if not self.editor or not self.pwfile:
1626 return self.PromptResult(current)
1627
1628 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001629 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001630 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1631 f.write("# (Additional spaces are harmless.)\n\n")
1632
1633 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001634 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001635 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001636 f.write("[[[ %s ]]] %s\n" % (v, k))
1637 if not v and first_line is None:
1638 # position cursor on first line with no password.
1639 first_line = i + 4
1640 f.close()
1641
Tao Bao986ee862018-10-04 15:46:16 -07001642 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001643
1644 return self.ReadFile()
1645
1646 def ReadFile(self):
1647 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001648 if self.pwfile is None:
1649 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001650 try:
1651 f = open(self.pwfile, "r")
1652 for line in f:
1653 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001654 if not line or line[0] == '#':
1655 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001656 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1657 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001658 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001659 else:
1660 result[m.group(2)] = m.group(1)
1661 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001662 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001663 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001664 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001665 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001666
1667
Dan Albert8e0178d2015-01-27 15:53:15 -08001668def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1669 compress_type=None):
1670 import datetime
1671
1672 # http://b/18015246
1673 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1674 # for files larger than 2GiB. We can work around this by adjusting their
1675 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1676 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1677 # it isn't clear to me exactly what circumstances cause this).
1678 # `zipfile.write()` must be used directly to work around this.
1679 #
1680 # This mess can be avoided if we port to python3.
1681 saved_zip64_limit = zipfile.ZIP64_LIMIT
1682 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1683
1684 if compress_type is None:
1685 compress_type = zip_file.compression
1686 if arcname is None:
1687 arcname = filename
1688
1689 saved_stat = os.stat(filename)
1690
1691 try:
1692 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1693 # file to be zipped and reset it when we're done.
1694 os.chmod(filename, perms)
1695
1696 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001697 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1698 # intentional. zip stores datetimes in local time without a time zone
1699 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1700 # in the zip archive.
1701 local_epoch = datetime.datetime.fromtimestamp(0)
1702 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001703 os.utime(filename, (timestamp, timestamp))
1704
1705 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1706 finally:
1707 os.chmod(filename, saved_stat.st_mode)
1708 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1709 zipfile.ZIP64_LIMIT = saved_zip64_limit
1710
1711
Tao Bao58c1b962015-05-20 09:32:18 -07001712def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001713 compress_type=None):
1714 """Wrap zipfile.writestr() function to work around the zip64 limit.
1715
1716 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1717 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1718 when calling crc32(bytes).
1719
1720 But it still works fine to write a shorter string into a large zip file.
1721 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1722 when we know the string won't be too long.
1723 """
1724
1725 saved_zip64_limit = zipfile.ZIP64_LIMIT
1726 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1727
1728 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1729 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001730 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001731 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001732 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001733 else:
Tao Baof3282b42015-04-01 11:21:55 -07001734 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001735 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1736 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1737 # such a case (since
1738 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1739 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1740 # permission bits. We follow the logic in Python 3 to get consistent
1741 # behavior between using the two versions.
1742 if not zinfo.external_attr:
1743 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001744
1745 # If compress_type is given, it overrides the value in zinfo.
1746 if compress_type is not None:
1747 zinfo.compress_type = compress_type
1748
Tao Bao58c1b962015-05-20 09:32:18 -07001749 # If perms is given, it has a priority.
1750 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001751 # If perms doesn't set the file type, mark it as a regular file.
1752 if perms & 0o770000 == 0:
1753 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001754 zinfo.external_attr = perms << 16
1755
Tao Baof3282b42015-04-01 11:21:55 -07001756 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001757 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1758
Dan Albert8b72aef2015-03-23 19:13:21 -07001759 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001760 zipfile.ZIP64_LIMIT = saved_zip64_limit
1761
1762
Tao Bao89d7ab22017-12-14 17:05:33 -08001763def ZipDelete(zip_filename, entries):
1764 """Deletes entries from a ZIP file.
1765
1766 Since deleting entries from a ZIP file is not supported, it shells out to
1767 'zip -d'.
1768
1769 Args:
1770 zip_filename: The name of the ZIP file.
1771 entries: The name of the entry, or the list of names to be deleted.
1772
1773 Raises:
1774 AssertionError: In case of non-zero return from 'zip'.
1775 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001776 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001777 entries = [entries]
1778 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001779 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001780
1781
Tao Baof3282b42015-04-01 11:21:55 -07001782def ZipClose(zip_file):
1783 # http://b/18015246
1784 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1785 # central directory.
1786 saved_zip64_limit = zipfile.ZIP64_LIMIT
1787 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1788
1789 zip_file.close()
1790
1791 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001792
1793
1794class DeviceSpecificParams(object):
1795 module = None
1796 def __init__(self, **kwargs):
1797 """Keyword arguments to the constructor become attributes of this
1798 object, which is passed to all functions in the device-specific
1799 module."""
Tao Bao38884282019-07-10 22:20:56 -07001800 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07001801 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001802 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001803
1804 if self.module is None:
1805 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001806 if not path:
1807 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001808 try:
1809 if os.path.isdir(path):
1810 info = imp.find_module("releasetools", [path])
1811 else:
1812 d, f = os.path.split(path)
1813 b, x = os.path.splitext(f)
1814 if x == ".py":
1815 f = b
1816 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001817 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001818 self.module = imp.load_module("device_specific", *info)
1819 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001820 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001821
1822 def _DoCall(self, function_name, *args, **kwargs):
1823 """Call the named function in the device-specific module, passing
1824 the given args and kwargs. The first argument to the call will be
1825 the DeviceSpecific object itself. If there is no module, or the
1826 module does not define the function, return the value of the
1827 'default' kwarg (which itself defaults to None)."""
1828 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001829 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001830 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1831
1832 def FullOTA_Assertions(self):
1833 """Called after emitting the block of assertions at the top of a
1834 full OTA package. Implementations can add whatever additional
1835 assertions they like."""
1836 return self._DoCall("FullOTA_Assertions")
1837
Doug Zongkere5ff5902012-01-17 10:55:37 -08001838 def FullOTA_InstallBegin(self):
1839 """Called at the start of full OTA installation."""
1840 return self._DoCall("FullOTA_InstallBegin")
1841
Yifan Hong10c530d2018-12-27 17:34:18 -08001842 def FullOTA_GetBlockDifferences(self):
1843 """Called during full OTA installation and verification.
1844 Implementation should return a list of BlockDifference objects describing
1845 the update on each additional partitions.
1846 """
1847 return self._DoCall("FullOTA_GetBlockDifferences")
1848
Doug Zongker05d3dea2009-06-22 11:32:31 -07001849 def FullOTA_InstallEnd(self):
1850 """Called at the end of full OTA installation; typically this is
1851 used to install the image for the device's baseband processor."""
1852 return self._DoCall("FullOTA_InstallEnd")
1853
1854 def IncrementalOTA_Assertions(self):
1855 """Called after emitting the block of assertions at the top of an
1856 incremental OTA package. Implementations can add whatever
1857 additional assertions they like."""
1858 return self._DoCall("IncrementalOTA_Assertions")
1859
Doug Zongkere5ff5902012-01-17 10:55:37 -08001860 def IncrementalOTA_VerifyBegin(self):
1861 """Called at the start of the verification phase of incremental
1862 OTA installation; additional checks can be placed here to abort
1863 the script before any changes are made."""
1864 return self._DoCall("IncrementalOTA_VerifyBegin")
1865
Doug Zongker05d3dea2009-06-22 11:32:31 -07001866 def IncrementalOTA_VerifyEnd(self):
1867 """Called at the end of the verification phase of incremental OTA
1868 installation; additional checks can be placed here to abort the
1869 script before any changes are made."""
1870 return self._DoCall("IncrementalOTA_VerifyEnd")
1871
Doug Zongkere5ff5902012-01-17 10:55:37 -08001872 def IncrementalOTA_InstallBegin(self):
1873 """Called at the start of incremental OTA installation (after
1874 verification is complete)."""
1875 return self._DoCall("IncrementalOTA_InstallBegin")
1876
Yifan Hong10c530d2018-12-27 17:34:18 -08001877 def IncrementalOTA_GetBlockDifferences(self):
1878 """Called during incremental OTA installation and verification.
1879 Implementation should return a list of BlockDifference objects describing
1880 the update on each additional partitions.
1881 """
1882 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1883
Doug Zongker05d3dea2009-06-22 11:32:31 -07001884 def IncrementalOTA_InstallEnd(self):
1885 """Called at the end of incremental OTA installation; typically
1886 this is used to install the image for the device's baseband
1887 processor."""
1888 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001889
Tao Bao9bc6bb22015-11-09 16:58:28 -08001890 def VerifyOTA_Assertions(self):
1891 return self._DoCall("VerifyOTA_Assertions")
1892
Tao Bao76def242017-11-21 09:25:31 -08001893
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001894class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001895 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001896 self.name = name
1897 self.data = data
1898 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001899 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001900 self.sha1 = sha1(data).hexdigest()
1901
1902 @classmethod
1903 def FromLocalFile(cls, name, diskname):
1904 f = open(diskname, "rb")
1905 data = f.read()
1906 f.close()
1907 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001908
1909 def WriteToTemp(self):
1910 t = tempfile.NamedTemporaryFile()
1911 t.write(self.data)
1912 t.flush()
1913 return t
1914
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001915 def WriteToDir(self, d):
1916 with open(os.path.join(d, self.name), "wb") as fp:
1917 fp.write(self.data)
1918
Geremy Condra36bd3652014-02-06 19:45:10 -08001919 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001920 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001921
Tao Bao76def242017-11-21 09:25:31 -08001922
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001923DIFF_PROGRAM_BY_EXT = {
1924 ".gz" : "imgdiff",
1925 ".zip" : ["imgdiff", "-z"],
1926 ".jar" : ["imgdiff", "-z"],
1927 ".apk" : ["imgdiff", "-z"],
1928 ".img" : "imgdiff",
1929 }
1930
Tao Bao76def242017-11-21 09:25:31 -08001931
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001932class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001933 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001934 self.tf = tf
1935 self.sf = sf
1936 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001937 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001938
1939 def ComputePatch(self):
1940 """Compute the patch (as a string of data) needed to turn sf into
1941 tf. Returns the same tuple as GetPatch()."""
1942
1943 tf = self.tf
1944 sf = self.sf
1945
Doug Zongker24cd2802012-08-14 16:36:15 -07001946 if self.diff_program:
1947 diff_program = self.diff_program
1948 else:
1949 ext = os.path.splitext(tf.name)[1]
1950 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001951
1952 ttemp = tf.WriteToTemp()
1953 stemp = sf.WriteToTemp()
1954
1955 ext = os.path.splitext(tf.name)[1]
1956
1957 try:
1958 ptemp = tempfile.NamedTemporaryFile()
1959 if isinstance(diff_program, list):
1960 cmd = copy.copy(diff_program)
1961 else:
1962 cmd = [diff_program]
1963 cmd.append(stemp.name)
1964 cmd.append(ttemp.name)
1965 cmd.append(ptemp.name)
1966 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001967 err = []
1968 def run():
1969 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001970 if e:
1971 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001972 th = threading.Thread(target=run)
1973 th.start()
1974 th.join(timeout=300) # 5 mins
1975 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001976 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001977 p.terminate()
1978 th.join(5)
1979 if th.is_alive():
1980 p.kill()
1981 th.join()
1982
Tianjie Xua2a9f992018-01-05 15:15:54 -08001983 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001984 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001985 self.patch = None
1986 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001987 diff = ptemp.read()
1988 finally:
1989 ptemp.close()
1990 stemp.close()
1991 ttemp.close()
1992
1993 self.patch = diff
1994 return self.tf, self.sf, self.patch
1995
1996
1997 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001998 """Returns a tuple of (target_file, source_file, patch_data).
1999
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002000 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002001 computing the patch failed.
2002 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002003 return self.tf, self.sf, self.patch
2004
2005
2006def ComputeDifferences(diffs):
2007 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002008 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002009
2010 # Do the largest files first, to try and reduce the long-pole effect.
2011 by_size = [(i.tf.size, i) for i in diffs]
2012 by_size.sort(reverse=True)
2013 by_size = [i[1] for i in by_size]
2014
2015 lock = threading.Lock()
2016 diff_iter = iter(by_size) # accessed under lock
2017
2018 def worker():
2019 try:
2020 lock.acquire()
2021 for d in diff_iter:
2022 lock.release()
2023 start = time.time()
2024 d.ComputePatch()
2025 dur = time.time() - start
2026 lock.acquire()
2027
2028 tf, sf, patch = d.GetPatch()
2029 if sf.name == tf.name:
2030 name = tf.name
2031 else:
2032 name = "%s (%s)" % (tf.name, sf.name)
2033 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002034 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002035 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002036 logger.info(
2037 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2038 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002039 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002040 except Exception:
2041 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002042 raise
2043
2044 # start worker threads; wait for them all to finish.
2045 threads = [threading.Thread(target=worker)
2046 for i in range(OPTIONS.worker_threads)]
2047 for th in threads:
2048 th.start()
2049 while threads:
2050 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002051
2052
Dan Albert8b72aef2015-03-23 19:13:21 -07002053class BlockDifference(object):
2054 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002055 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002056 self.tgt = tgt
2057 self.src = src
2058 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002059 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002060 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002061
Tao Baodd2a5892015-03-12 12:32:37 -07002062 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002063 version = max(
2064 int(i) for i in
2065 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002066 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002067 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002068
Tianjie Xu41976c72019-07-03 13:57:01 -07002069 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2070 version=self.version,
2071 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002072 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002073 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002074 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002075 self.touched_src_ranges = b.touched_src_ranges
2076 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002077
Yifan Hong10c530d2018-12-27 17:34:18 -08002078 # On devices with dynamic partitions, for new partitions,
2079 # src is None but OPTIONS.source_info_dict is not.
2080 if OPTIONS.source_info_dict is None:
2081 is_dynamic_build = OPTIONS.info_dict.get(
2082 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002083 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002084 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002085 is_dynamic_build = OPTIONS.source_info_dict.get(
2086 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002087 is_dynamic_source = partition in shlex.split(
2088 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002089
Yifan Hongbb2658d2019-01-25 12:30:58 -08002090 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002091 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2092
Yifan Hongbb2658d2019-01-25 12:30:58 -08002093 # For dynamic partitions builds, check partition list in both source
2094 # and target build because new partitions may be added, and existing
2095 # partitions may be removed.
2096 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2097
Yifan Hong10c530d2018-12-27 17:34:18 -08002098 if is_dynamic:
2099 self.device = 'map_partition("%s")' % partition
2100 else:
2101 if OPTIONS.source_info_dict is None:
2102 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2103 else:
2104 _, device_path = GetTypeAndDevice("/" + partition,
2105 OPTIONS.source_info_dict)
2106 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002107
Tao Baod8d14be2016-02-04 14:26:02 -08002108 @property
2109 def required_cache(self):
2110 return self._required_cache
2111
Tao Bao76def242017-11-21 09:25:31 -08002112 def WriteScript(self, script, output_zip, progress=None,
2113 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002114 if not self.src:
2115 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002116 script.Print("Patching %s image unconditionally..." % (self.partition,))
2117 else:
2118 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002119
Dan Albert8b72aef2015-03-23 19:13:21 -07002120 if progress:
2121 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002122 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002123
2124 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002125 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002126
Tao Bao9bc6bb22015-11-09 16:58:28 -08002127 def WriteStrictVerifyScript(self, script):
2128 """Verify all the blocks in the care_map, including clobbered blocks.
2129
2130 This differs from the WriteVerifyScript() function: a) it prints different
2131 error messages; b) it doesn't allow half-way updated images to pass the
2132 verification."""
2133
2134 partition = self.partition
2135 script.Print("Verifying %s..." % (partition,))
2136 ranges = self.tgt.care_map
2137 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002138 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002139 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2140 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002141 self.device, ranges_str,
2142 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002143 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002144 script.AppendExtra("")
2145
Tao Baod522bdc2016-04-12 15:53:16 -07002146 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002147 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002148
2149 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002150 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002151 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002152
2153 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002154 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002155 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002156 ranges = self.touched_src_ranges
2157 expected_sha1 = self.touched_src_sha1
2158 else:
2159 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2160 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002161
2162 # No blocks to be checked, skipping.
2163 if not ranges:
2164 return
2165
Tao Bao5ece99d2015-05-12 11:42:31 -07002166 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002167 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002168 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002169 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2170 '"%s.patch.dat")) then' % (
2171 self.device, ranges_str, expected_sha1,
2172 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002173 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002174 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002175
Tianjie Xufc3422a2015-12-15 11:53:59 -08002176 if self.version >= 4:
2177
2178 # Bug: 21124327
2179 # When generating incrementals for the system and vendor partitions in
2180 # version 4 or newer, explicitly check the first block (which contains
2181 # the superblock) of the partition to see if it's what we expect. If
2182 # this check fails, give an explicit log message about the partition
2183 # having been remounted R/W (the most likely explanation).
2184 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002185 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002186
2187 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002188 if partition == "system":
2189 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2190 else:
2191 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002192 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002193 'ifelse (block_image_recover({device}, "{ranges}") && '
2194 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002195 'package_extract_file("{partition}.transfer.list"), '
2196 '"{partition}.new.dat", "{partition}.patch.dat"), '
2197 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002198 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002199 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002200 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002201
Tao Baodd2a5892015-03-12 12:32:37 -07002202 # Abort the OTA update. Note that the incremental OTA cannot be applied
2203 # even if it may match the checksum of the target partition.
2204 # a) If version < 3, operations like move and erase will make changes
2205 # unconditionally and damage the partition.
2206 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002207 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002208 if partition == "system":
2209 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2210 else:
2211 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2212 script.AppendExtra((
2213 'abort("E%d: %s partition has unexpected contents");\n'
2214 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002215
Yifan Hong10c530d2018-12-27 17:34:18 -08002216 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002217 partition = self.partition
2218 script.Print('Verifying the updated %s image...' % (partition,))
2219 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2220 ranges = self.tgt.care_map
2221 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002222 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002223 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002224 self.device, ranges_str,
2225 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002226
2227 # Bug: 20881595
2228 # Verify that extended blocks are really zeroed out.
2229 if self.tgt.extended:
2230 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002231 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002232 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002233 self.device, ranges_str,
2234 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002235 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002236 if partition == "system":
2237 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2238 else:
2239 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002240 script.AppendExtra(
2241 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002242 ' abort("E%d: %s partition has unexpected non-zero contents after '
2243 'OTA update");\n'
2244 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002245 else:
2246 script.Print('Verified the updated %s image.' % (partition,))
2247
Tianjie Xu209db462016-05-24 17:34:52 -07002248 if partition == "system":
2249 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2250 else:
2251 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2252
Tao Bao5fcaaef2015-06-01 13:40:49 -07002253 script.AppendExtra(
2254 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002255 ' abort("E%d: %s partition has unexpected contents after OTA '
2256 'update");\n'
2257 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002258
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002259 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002260 ZipWrite(output_zip,
2261 '{}.transfer.list'.format(self.path),
2262 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002263
Tao Bao76def242017-11-21 09:25:31 -08002264 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2265 # its size. Quailty 9 almost triples the compression time but doesn't
2266 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002267 # zip | brotli(quality 6) | brotli(quality 9)
2268 # compressed_size: 942M | 869M (~8% reduced) | 854M
2269 # compression_time: 75s | 265s | 719s
2270 # decompression_time: 15s | 25s | 25s
2271
2272 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002273 brotli_cmd = ['brotli', '--quality=6',
2274 '--output={}.new.dat.br'.format(self.path),
2275 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002276 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002277 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002278
2279 new_data_name = '{}.new.dat.br'.format(self.partition)
2280 ZipWrite(output_zip,
2281 '{}.new.dat.br'.format(self.path),
2282 new_data_name,
2283 compress_type=zipfile.ZIP_STORED)
2284 else:
2285 new_data_name = '{}.new.dat'.format(self.partition)
2286 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2287
Dan Albert8e0178d2015-01-27 15:53:15 -08002288 ZipWrite(output_zip,
2289 '{}.patch.dat'.format(self.path),
2290 '{}.patch.dat'.format(self.partition),
2291 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002292
Tianjie Xu209db462016-05-24 17:34:52 -07002293 if self.partition == "system":
2294 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2295 else:
2296 code = ErrorCode.VENDOR_UPDATE_FAILURE
2297
Yifan Hong10c530d2018-12-27 17:34:18 -08002298 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002299 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002300 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002301 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002302 device=self.device, partition=self.partition,
2303 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002304 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002305
Dan Albert8b72aef2015-03-23 19:13:21 -07002306 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002307 data = source.ReadRangeSet(ranges)
2308 ctx = sha1()
2309
2310 for p in data:
2311 ctx.update(p)
2312
2313 return ctx.hexdigest()
2314
Tao Baoe9b61912015-07-09 17:37:49 -07002315 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2316 """Return the hash value for all zero blocks."""
2317 zero_block = '\x00' * 4096
2318 ctx = sha1()
2319 for _ in range(num_blocks):
2320 ctx.update(zero_block)
2321
2322 return ctx.hexdigest()
2323
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002324
Tianjie Xu41976c72019-07-03 13:57:01 -07002325# Expose these two classes to support vendor-specific scripts
2326DataImage = images.DataImage
2327EmptyImage = images.EmptyImage
2328
Tao Bao76def242017-11-21 09:25:31 -08002329
Doug Zongker96a57e72010-09-26 14:57:41 -07002330# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002331PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002332 "ext4": "EMMC",
2333 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002334 "f2fs": "EMMC",
2335 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002336}
Doug Zongker96a57e72010-09-26 14:57:41 -07002337
Tao Bao76def242017-11-21 09:25:31 -08002338
Doug Zongker96a57e72010-09-26 14:57:41 -07002339def GetTypeAndDevice(mount_point, info):
2340 fstab = info["fstab"]
2341 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002342 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2343 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002344 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002345 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002346
2347
2348def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002349 """Parses and converts a PEM-encoded certificate into DER-encoded.
2350
2351 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2352
2353 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002354 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002355 """
2356 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002357 save = False
2358 for line in data.split("\n"):
2359 if "--END CERTIFICATE--" in line:
2360 break
2361 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002362 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002363 if "--BEGIN CERTIFICATE--" in line:
2364 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002365 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002366 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002367
Tao Bao04e1f012018-02-04 12:13:35 -08002368
2369def ExtractPublicKey(cert):
2370 """Extracts the public key (PEM-encoded) from the given certificate file.
2371
2372 Args:
2373 cert: The certificate filename.
2374
2375 Returns:
2376 The public key string.
2377
2378 Raises:
2379 AssertionError: On non-zero return from 'openssl'.
2380 """
2381 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2382 # While openssl 1.1 writes the key into the given filename followed by '-out',
2383 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2384 # stdout instead.
2385 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2386 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2387 pubkey, stderrdata = proc.communicate()
2388 assert proc.returncode == 0, \
2389 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2390 return pubkey
2391
2392
Tao Bao1ac886e2019-06-26 11:58:22 -07002393def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002394 """Extracts the AVB public key from the given public or private key.
2395
2396 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002397 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002398 key: The input key file, which should be PEM-encoded public or private key.
2399
2400 Returns:
2401 The path to the extracted AVB public key file.
2402 """
2403 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2404 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002405 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002406 return output
2407
2408
Doug Zongker412c02f2014-02-13 10:58:24 -08002409def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2410 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002411 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002412
Tao Bao6d5d6232018-03-09 17:04:42 -08002413 Most of the space in the boot and recovery images is just the kernel, which is
2414 identical for the two, so the resulting patch should be efficient. Add it to
2415 the output zip, along with a shell script that is run from init.rc on first
2416 boot to actually do the patching and install the new recovery image.
2417
2418 Args:
2419 input_dir: The top-level input directory of the target-files.zip.
2420 output_sink: The callback function that writes the result.
2421 recovery_img: File object for the recovery image.
2422 boot_img: File objects for the boot image.
2423 info_dict: A dict returned by common.LoadInfoDict() on the input
2424 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002425 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002426 if info_dict is None:
2427 info_dict = OPTIONS.info_dict
2428
Tao Bao6d5d6232018-03-09 17:04:42 -08002429 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002430
Tao Baof2cffbd2015-07-22 12:33:18 -07002431 if full_recovery_image:
2432 output_sink("etc/recovery.img", recovery_img.data)
2433
2434 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002435 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002436 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002437 # With system-root-image, boot and recovery images will have mismatching
2438 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2439 # to handle such a case.
2440 if system_root_image:
2441 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002442 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002443 assert not os.path.exists(path)
2444 else:
2445 diff_program = ["imgdiff"]
2446 if os.path.exists(path):
2447 diff_program.append("-b")
2448 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002449 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002450 else:
2451 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002452
2453 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2454 _, _, patch = d.ComputePatch()
2455 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002456
Dan Albertebb19aa2015-03-27 19:11:53 -07002457 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002458 # The following GetTypeAndDevice()s need to use the path in the target
2459 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002460 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2461 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2462 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002463 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002464
Tao Baof2cffbd2015-07-22 12:33:18 -07002465 if full_recovery_image:
2466 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002467if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2468 applypatch \\
2469 --flash /system/etc/recovery.img \\
2470 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2471 log -t recovery "Installing new recovery image: succeeded" || \\
2472 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002473else
2474 log -t recovery "Recovery image already installed"
2475fi
2476""" % {'type': recovery_type,
2477 'device': recovery_device,
2478 'sha1': recovery_img.sha1,
2479 'size': recovery_img.size}
2480 else:
2481 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002482if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2483 applypatch %(bonus_args)s \\
2484 --patch /system/recovery-from-boot.p \\
2485 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2486 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2487 log -t recovery "Installing new recovery image: succeeded" || \\
2488 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002489else
2490 log -t recovery "Recovery image already installed"
2491fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002492""" % {'boot_size': boot_img.size,
2493 'boot_sha1': boot_img.sha1,
2494 'recovery_size': recovery_img.size,
2495 'recovery_sha1': recovery_img.sha1,
2496 'boot_type': boot_type,
2497 'boot_device': boot_device,
2498 'recovery_type': recovery_type,
2499 'recovery_device': recovery_device,
2500 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002501
2502 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002503 # in the L release.
2504 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002505
Tao Bao32fcdab2018-10-12 10:30:39 -07002506 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002507
Tao Baoda30cfa2017-12-01 16:19:46 -08002508 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002509
2510
2511class DynamicPartitionUpdate(object):
2512 def __init__(self, src_group=None, tgt_group=None, progress=None,
2513 block_difference=None):
2514 self.src_group = src_group
2515 self.tgt_group = tgt_group
2516 self.progress = progress
2517 self.block_difference = block_difference
2518
2519 @property
2520 def src_size(self):
2521 if not self.block_difference:
2522 return 0
2523 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2524
2525 @property
2526 def tgt_size(self):
2527 if not self.block_difference:
2528 return 0
2529 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2530
2531 @staticmethod
2532 def _GetSparseImageSize(img):
2533 if not img:
2534 return 0
2535 return img.blocksize * img.total_blocks
2536
2537
2538class DynamicGroupUpdate(object):
2539 def __init__(self, src_size=None, tgt_size=None):
2540 # None: group does not exist. 0: no size limits.
2541 self.src_size = src_size
2542 self.tgt_size = tgt_size
2543
2544
2545class DynamicPartitionsDifference(object):
2546 def __init__(self, info_dict, block_diffs, progress_dict=None,
2547 source_info_dict=None):
2548 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002549 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002550
2551 self._remove_all_before_apply = False
2552 if source_info_dict is None:
2553 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002554 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002555
Tao Baof1113e92019-06-18 12:10:14 -07002556 block_diff_dict = collections.OrderedDict(
2557 [(e.partition, e) for e in block_diffs])
2558
Yifan Hong10c530d2018-12-27 17:34:18 -08002559 assert len(block_diff_dict) == len(block_diffs), \
2560 "Duplicated BlockDifference object for {}".format(
2561 [partition for partition, count in
2562 collections.Counter(e.partition for e in block_diffs).items()
2563 if count > 1])
2564
Yifan Hong79997e52019-01-23 16:56:19 -08002565 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002566
2567 for p, block_diff in block_diff_dict.items():
2568 self._partition_updates[p] = DynamicPartitionUpdate()
2569 self._partition_updates[p].block_difference = block_diff
2570
2571 for p, progress in progress_dict.items():
2572 if p in self._partition_updates:
2573 self._partition_updates[p].progress = progress
2574
2575 tgt_groups = shlex.split(info_dict.get(
2576 "super_partition_groups", "").strip())
2577 src_groups = shlex.split(source_info_dict.get(
2578 "super_partition_groups", "").strip())
2579
2580 for g in tgt_groups:
2581 for p in shlex.split(info_dict.get(
2582 "super_%s_partition_list" % g, "").strip()):
2583 assert p in self._partition_updates, \
2584 "{} is in target super_{}_partition_list but no BlockDifference " \
2585 "object is provided.".format(p, g)
2586 self._partition_updates[p].tgt_group = g
2587
2588 for g in src_groups:
2589 for p in shlex.split(source_info_dict.get(
2590 "super_%s_partition_list" % g, "").strip()):
2591 assert p in self._partition_updates, \
2592 "{} is in source super_{}_partition_list but no BlockDifference " \
2593 "object is provided.".format(p, g)
2594 self._partition_updates[p].src_group = g
2595
Yifan Hong45433e42019-01-18 13:55:25 -08002596 target_dynamic_partitions = set(shlex.split(info_dict.get(
2597 "dynamic_partition_list", "").strip()))
2598 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2599 if u.tgt_size)
2600 assert block_diffs_with_target == target_dynamic_partitions, \
2601 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2602 list(target_dynamic_partitions), list(block_diffs_with_target))
2603
2604 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2605 "dynamic_partition_list", "").strip()))
2606 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2607 if u.src_size)
2608 assert block_diffs_with_source == source_dynamic_partitions, \
2609 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2610 list(source_dynamic_partitions), list(block_diffs_with_source))
2611
Yifan Hong10c530d2018-12-27 17:34:18 -08002612 if self._partition_updates:
2613 logger.info("Updating dynamic partitions %s",
2614 self._partition_updates.keys())
2615
Yifan Hong79997e52019-01-23 16:56:19 -08002616 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002617
2618 for g in tgt_groups:
2619 self._group_updates[g] = DynamicGroupUpdate()
2620 self._group_updates[g].tgt_size = int(info_dict.get(
2621 "super_%s_group_size" % g, "0").strip())
2622
2623 for g in src_groups:
2624 if g not in self._group_updates:
2625 self._group_updates[g] = DynamicGroupUpdate()
2626 self._group_updates[g].src_size = int(source_info_dict.get(
2627 "super_%s_group_size" % g, "0").strip())
2628
2629 self._Compute()
2630
2631 def WriteScript(self, script, output_zip, write_verify_script=False):
2632 script.Comment('--- Start patching dynamic partitions ---')
2633 for p, u in self._partition_updates.items():
2634 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2635 script.Comment('Patch partition %s' % p)
2636 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2637 write_verify_script=False)
2638
2639 op_list_path = MakeTempFile()
2640 with open(op_list_path, 'w') as f:
2641 for line in self._op_list:
2642 f.write('{}\n'.format(line))
2643
2644 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2645
2646 script.Comment('Update dynamic partition metadata')
2647 script.AppendExtra('assert(update_dynamic_partitions('
2648 'package_extract_file("dynamic_partitions_op_list")));')
2649
2650 if write_verify_script:
2651 for p, u in self._partition_updates.items():
2652 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2653 u.block_difference.WritePostInstallVerifyScript(script)
2654 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2655
2656 for p, u in self._partition_updates.items():
2657 if u.tgt_size and u.src_size <= u.tgt_size:
2658 script.Comment('Patch partition %s' % p)
2659 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2660 write_verify_script=write_verify_script)
2661 if write_verify_script:
2662 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2663
2664 script.Comment('--- End patching dynamic partitions ---')
2665
2666 def _Compute(self):
2667 self._op_list = list()
2668
2669 def append(line):
2670 self._op_list.append(line)
2671
2672 def comment(line):
2673 self._op_list.append("# %s" % line)
2674
2675 if self._remove_all_before_apply:
2676 comment('Remove all existing dynamic partitions and groups before '
2677 'applying full OTA')
2678 append('remove_all_groups')
2679
2680 for p, u in self._partition_updates.items():
2681 if u.src_group and not u.tgt_group:
2682 append('remove %s' % p)
2683
2684 for p, u in self._partition_updates.items():
2685 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2686 comment('Move partition %s from %s to default' % (p, u.src_group))
2687 append('move %s default' % p)
2688
2689 for p, u in self._partition_updates.items():
2690 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2691 comment('Shrink partition %s from %d to %d' %
2692 (p, u.src_size, u.tgt_size))
2693 append('resize %s %s' % (p, u.tgt_size))
2694
2695 for g, u in self._group_updates.items():
2696 if u.src_size is not None and u.tgt_size is None:
2697 append('remove_group %s' % g)
2698 if (u.src_size is not None and u.tgt_size is not None and
2699 u.src_size > u.tgt_size):
2700 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2701 append('resize_group %s %d' % (g, u.tgt_size))
2702
2703 for g, u in self._group_updates.items():
2704 if u.src_size is None and u.tgt_size is not None:
2705 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2706 append('add_group %s %d' % (g, u.tgt_size))
2707 if (u.src_size is not None and u.tgt_size is not None and
2708 u.src_size < u.tgt_size):
2709 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2710 append('resize_group %s %d' % (g, u.tgt_size))
2711
2712 for p, u in self._partition_updates.items():
2713 if u.tgt_group and not u.src_group:
2714 comment('Add partition %s to group %s' % (p, u.tgt_group))
2715 append('add %s %s' % (p, u.tgt_group))
2716
2717 for p, u in self._partition_updates.items():
2718 if u.tgt_size and u.src_size < u.tgt_size:
2719 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2720 append('resize %s %d' % (p, u.tgt_size))
2721
2722 for p, u in self._partition_updates.items():
2723 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2724 comment('Move partition %s from default to %s' %
2725 (p, u.tgt_group))
2726 append('move %s %s' % (p, u.tgt_group))