blob: 1175688921f3373c20d1d9a2b147b59def2e2630 [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
Tao Bao02a08592018-07-22 12:40:45 -0700628def GetAvbChainedPartitionArg(partition, info_dict, key=None):
629 """Constructs and returns the arg to build or verify a chained partition.
630
631 Args:
632 partition: The partition name.
633 info_dict: The info dict to look up the key info and rollback index
634 location.
635 key: The key to be used for building or verifying the partition. Defaults to
636 the key listed in info_dict.
637
638 Returns:
639 A string of form "partition:rollback_index_location:key" that can be used to
640 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700641 """
642 if key is None:
643 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao1ac886e2019-06-26 11:58:22 -0700644 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700645 rollback_index_location = info_dict[
646 "avb_" + partition + "_rollback_index_location"]
647 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
648
649
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700650def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800651 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700652 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700653
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700654 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800655 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
656 we are building a two-step special image (i.e. building a recovery image to
657 be loaded into /boot in two-step OTAs).
658
659 Return the image data, or None if sourcedir does not appear to contains files
660 for building the requested image.
661 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700662
663 def make_ramdisk():
664 ramdisk_img = tempfile.NamedTemporaryFile()
665
666 if os.access(fs_config_file, os.F_OK):
667 cmd = ["mkbootfs", "-f", fs_config_file,
668 os.path.join(sourcedir, "RAMDISK")]
669 else:
670 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
671 p1 = Run(cmd, stdout=subprocess.PIPE)
672 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
673
674 p2.wait()
675 p1.wait()
676 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
677 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
678
679 return ramdisk_img
680
681 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
682 return None
683
684 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700685 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700686
Doug Zongkerd5131602012-08-02 14:46:42 -0700687 if info_dict is None:
688 info_dict = OPTIONS.info_dict
689
Doug Zongkereef39442009-04-02 12:14:19 -0700690 img = tempfile.NamedTemporaryFile()
691
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700692 if has_ramdisk:
693 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700694
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800695 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
696 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
697
698 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700699
Benoit Fradina45a8682014-07-14 21:00:43 +0200700 fn = os.path.join(sourcedir, "second")
701 if os.access(fn, os.F_OK):
702 cmd.append("--second")
703 cmd.append(fn)
704
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800705 fn = os.path.join(sourcedir, "dtb")
706 if os.access(fn, os.F_OK):
707 cmd.append("--dtb")
708 cmd.append(fn)
709
Doug Zongker171f1cd2009-06-15 22:36:37 -0700710 fn = os.path.join(sourcedir, "cmdline")
711 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700712 cmd.append("--cmdline")
713 cmd.append(open(fn).read().rstrip("\n"))
714
715 fn = os.path.join(sourcedir, "base")
716 if os.access(fn, os.F_OK):
717 cmd.append("--base")
718 cmd.append(open(fn).read().rstrip("\n"))
719
Ying Wang4de6b5b2010-08-25 14:29:34 -0700720 fn = os.path.join(sourcedir, "pagesize")
721 if os.access(fn, os.F_OK):
722 cmd.append("--pagesize")
723 cmd.append(open(fn).read().rstrip("\n"))
724
Tao Bao76def242017-11-21 09:25:31 -0800725 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700726 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700727 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700728
Tao Bao76def242017-11-21 09:25:31 -0800729 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000730 if args and args.strip():
731 cmd.extend(shlex.split(args))
732
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700733 if has_ramdisk:
734 cmd.extend(["--ramdisk", ramdisk_img.name])
735
Tao Baod95e9fd2015-03-29 23:07:41 -0700736 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800737 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700738 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700739 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700740 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700741 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700742
Tao Baobf70c312017-07-11 17:27:55 -0700743 # "boot" or "recovery", without extension.
744 partition_name = os.path.basename(sourcedir).lower()
745
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800746 if partition_name == "recovery":
747 if info_dict.get("include_recovery_dtbo") == "true":
748 fn = os.path.join(sourcedir, "recovery_dtbo")
749 cmd.extend(["--recovery_dtbo", fn])
750 if info_dict.get("include_recovery_acpio") == "true":
751 fn = os.path.join(sourcedir, "recovery_acpio")
752 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700753
Tao Bao986ee862018-10-04 15:46:16 -0700754 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700755
Tao Bao76def242017-11-21 09:25:31 -0800756 if (info_dict.get("boot_signer") == "true" and
757 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800758 # Hard-code the path as "/boot" for two-step special recovery image (which
759 # will be loaded into /boot during the two-step OTA).
760 if two_step_image:
761 path = "/boot"
762 else:
Tao Baobf70c312017-07-11 17:27:55 -0700763 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700764 cmd = [OPTIONS.boot_signer_path]
765 cmd.extend(OPTIONS.boot_signer_args)
766 cmd.extend([path, img.name,
767 info_dict["verity_key"] + ".pk8",
768 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700769 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700770
Tao Baod95e9fd2015-03-29 23:07:41 -0700771 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800772 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700773 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700774 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800775 # We have switched from the prebuilt futility binary to using the tool
776 # (futility-host) built from the source. Override the setting in the old
777 # TF.zip.
778 futility = info_dict["futility"]
779 if futility.startswith("prebuilts/"):
780 futility = "futility-host"
781 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700782 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700783 info_dict["vboot_key"] + ".vbprivk",
784 info_dict["vboot_subkey"] + ".vbprivk",
785 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700786 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700787 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700788
Tao Baof3282b42015-04-01 11:21:55 -0700789 # Clean up the temp files.
790 img_unsigned.close()
791 img_keyblock.close()
792
David Zeuthen8fecb282017-12-01 16:24:01 -0500793 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800794 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700795 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500796 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400797 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700798 "--partition_size", str(part_size), "--partition_name",
799 partition_name]
800 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500801 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400802 if args and args.strip():
803 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700804 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500805
806 img.seek(os.SEEK_SET, 0)
807 data = img.read()
808
809 if has_ramdisk:
810 ramdisk_img.close()
811 img.close()
812
813 return data
814
815
Doug Zongkerd5131602012-08-02 14:46:42 -0700816def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800817 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700818 """Return a File object with the desired bootable image.
819
820 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
821 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
822 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700823
Doug Zongker55d93282011-01-25 17:03:34 -0800824 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
825 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700826 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800827 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700828
829 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
830 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700831 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700832 return File.FromLocalFile(name, prebuilt_path)
833
Tao Bao32fcdab2018-10-12 10:30:39 -0700834 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700835
836 if info_dict is None:
837 info_dict = OPTIONS.info_dict
838
839 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800840 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
841 # for recovery.
842 has_ramdisk = (info_dict.get("system_root_image") != "true" or
843 prebuilt_name != "boot.img" or
844 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700845
Doug Zongker6f1d0312014-08-22 08:07:12 -0700846 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400847 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
848 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800849 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700850 if data:
851 return File(name, data)
852 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800853
Doug Zongkereef39442009-04-02 12:14:19 -0700854
Narayan Kamatha07bf042017-08-14 14:49:21 +0100855def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800856 """Gunzips the given gzip compressed file to a given output file."""
857 with gzip.open(in_filename, "rb") as in_file, \
858 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100859 shutil.copyfileobj(in_file, out_file)
860
861
Tao Bao0ff15de2019-03-20 11:26:06 -0700862def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800863 """Unzips the archive to the given directory.
864
865 Args:
866 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800867 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700868 patterns: Files to unzip from the archive. If omitted, will unzip the entire
869 archvie. Non-matching patterns will be filtered out. If there's no match
870 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800871 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800872 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700873 if patterns is not None:
874 # Filter out non-matching patterns. unzip will complain otherwise.
875 with zipfile.ZipFile(filename) as input_zip:
876 names = input_zip.namelist()
877 filtered = [
878 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
879
880 # There isn't any matching files. Don't unzip anything.
881 if not filtered:
882 return
883 cmd.extend(filtered)
884
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800885 RunAndCheckOutput(cmd)
886
887
Doug Zongker75f17362009-12-08 13:46:44 -0800888def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800889 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800890
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800891 Args:
892 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
893 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
894
895 pattern: Files to unzip from the archive. If omitted, will unzip the entire
896 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800897
Tao Bao1c830bf2017-12-25 10:43:47 -0800898 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800899 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800900 """
Doug Zongkereef39442009-04-02 12:14:19 -0700901
Tao Bao1c830bf2017-12-25 10:43:47 -0800902 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800903 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
904 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800905 UnzipToDir(m.group(1), tmp, pattern)
906 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800907 filename = m.group(1)
908 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800909 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800910
Tao Baodba59ee2018-01-09 13:21:02 -0800911 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700912
913
Yifan Hong8a66a712019-04-04 15:37:57 -0700914def GetUserImage(which, tmpdir, input_zip,
915 info_dict=None,
916 allow_shared_blocks=None,
917 hashtree_info_generator=None,
918 reset_file_map=False):
919 """Returns an Image object suitable for passing to BlockImageDiff.
920
921 This function loads the specified image from the given path. If the specified
922 image is sparse, it also performs additional processing for OTA purpose. For
923 example, it always adds block 0 to clobbered blocks list. It also detects
924 files that cannot be reconstructed from the block list, for whom we should
925 avoid applying imgdiff.
926
927 Args:
928 which: The partition name.
929 tmpdir: The directory that contains the prebuilt image and block map file.
930 input_zip: The target-files ZIP archive.
931 info_dict: The dict to be looked up for relevant info.
932 allow_shared_blocks: If image is sparse, whether having shared blocks is
933 allowed. If none, it is looked up from info_dict.
934 hashtree_info_generator: If present and image is sparse, generates the
935 hashtree_info for this sparse image.
936 reset_file_map: If true and image is sparse, reset file map before returning
937 the image.
938 Returns:
939 A Image object. If it is a sparse image and reset_file_map is False, the
940 image will have file_map info loaded.
941 """
Tao Baoc1a1ec32019-06-18 16:29:37 -0700942 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -0700943 info_dict = LoadInfoDict(input_zip)
944
945 is_sparse = info_dict.get("extfs_sparse_flag")
946
947 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
948 # shared blocks (i.e. some blocks will show up in multiple files' block
949 # list). We can only allocate such shared blocks to the first "owner", and
950 # disable imgdiff for all later occurrences.
951 if allow_shared_blocks is None:
952 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
953
954 if is_sparse:
955 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
956 hashtree_info_generator)
957 if reset_file_map:
958 img.ResetFileMap()
959 return img
960 else:
961 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
962
963
964def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
965 """Returns a Image object suitable for passing to BlockImageDiff.
966
967 This function loads the specified non-sparse image from the given path.
968
969 Args:
970 which: The partition name.
971 tmpdir: The directory that contains the prebuilt image and block map file.
972 Returns:
973 A Image object.
974 """
975 path = os.path.join(tmpdir, "IMAGES", which + ".img")
976 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
977
978 # The image and map files must have been created prior to calling
979 # ota_from_target_files.py (since LMP).
980 assert os.path.exists(path) and os.path.exists(mappath)
981
Tianjie Xu41976c72019-07-03 13:57:01 -0700982 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
983
Yifan Hong8a66a712019-04-04 15:37:57 -0700984
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700985def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
986 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800987 """Returns a SparseImage object suitable for passing to BlockImageDiff.
988
989 This function loads the specified sparse image from the given path, and
990 performs additional processing for OTA purpose. For example, it always adds
991 block 0 to clobbered blocks list. It also detects files that cannot be
992 reconstructed from the block list, for whom we should avoid applying imgdiff.
993
994 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700995 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800996 tmpdir: The directory that contains the prebuilt image and block map file.
997 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800998 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700999 hashtree_info_generator: If present, generates the hashtree_info for this
1000 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001001 Returns:
1002 A SparseImage object, with file_map info loaded.
1003 """
Tao Baoc765cca2018-01-31 17:32:40 -08001004 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1005 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1006
1007 # The image and map files must have been created prior to calling
1008 # ota_from_target_files.py (since LMP).
1009 assert os.path.exists(path) and os.path.exists(mappath)
1010
1011 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1012 # it to clobbered_blocks so that it will be written to the target
1013 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1014 clobbered_blocks = "0"
1015
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001016 image = sparse_img.SparseImage(
1017 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1018 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001019
1020 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1021 # if they contain all zeros. We can't reconstruct such a file from its block
1022 # list. Tag such entries accordingly. (Bug: 65213616)
1023 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001024 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001025 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001026 continue
1027
Tom Cherryd14b8952018-08-09 14:26:00 -07001028 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1029 # filename listed in system.map may contain an additional leading slash
1030 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1031 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001032 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001033
Tom Cherryd14b8952018-08-09 14:26:00 -07001034 # Special handling another case, where files not under /system
1035 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001036 if which == 'system' and not arcname.startswith('SYSTEM'):
1037 arcname = 'ROOT/' + arcname
1038
1039 assert arcname in input_zip.namelist(), \
1040 "Failed to find the ZIP entry for {}".format(entry)
1041
Tao Baoc765cca2018-01-31 17:32:40 -08001042 info = input_zip.getinfo(arcname)
1043 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001044
1045 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001046 # image, check the original block list to determine its completeness. Note
1047 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001048 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001049 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001050
Tao Baoc765cca2018-01-31 17:32:40 -08001051 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1052 ranges.extra['incomplete'] = True
1053
1054 return image
1055
1056
Doug Zongkereef39442009-04-02 12:14:19 -07001057def GetKeyPasswords(keylist):
1058 """Given a list of keys, prompt the user to enter passwords for
1059 those which require them. Return a {key: password} dict. password
1060 will be None if the key has no password."""
1061
Doug Zongker8ce7c252009-05-22 13:34:54 -07001062 no_passwords = []
1063 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001064 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001065 devnull = open("/dev/null", "w+b")
1066 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001067 # We don't need a password for things that aren't really keys.
1068 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001069 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001070 continue
1071
T.R. Fullhart37e10522013-03-18 10:31:26 -07001072 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001073 "-inform", "DER", "-nocrypt"],
1074 stdin=devnull.fileno(),
1075 stdout=devnull.fileno(),
1076 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001077 p.communicate()
1078 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001079 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001080 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001081 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001082 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1083 "-inform", "DER", "-passin", "pass:"],
1084 stdin=devnull.fileno(),
1085 stdout=devnull.fileno(),
1086 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001087 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001088 if p.returncode == 0:
1089 # Encrypted key with empty string as password.
1090 key_passwords[k] = ''
1091 elif stderr.startswith('Error decrypting key'):
1092 # Definitely encrypted key.
1093 # It would have said "Error reading key" if it didn't parse correctly.
1094 need_passwords.append(k)
1095 else:
1096 # Potentially, a type of key that openssl doesn't understand.
1097 # We'll let the routines in signapk.jar handle it.
1098 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001099 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001100
T.R. Fullhart37e10522013-03-18 10:31:26 -07001101 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001102 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001103 return key_passwords
1104
1105
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001106def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001107 """Gets the minSdkVersion declared in the APK.
1108
1109 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1110 This can be both a decimal number (API Level) or a codename.
1111
1112 Args:
1113 apk_name: The APK filename.
1114
1115 Returns:
1116 The parsed SDK version string.
1117
1118 Raises:
1119 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001120 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001121 proc = Run(
1122 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1123 stderr=subprocess.PIPE)
1124 stdoutdata, stderrdata = proc.communicate()
1125 if proc.returncode != 0:
1126 raise ExternalError(
1127 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1128 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001129
Tao Baof47bf0f2018-03-21 23:28:51 -07001130 for line in stdoutdata.split("\n"):
1131 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001132 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1133 if m:
1134 return m.group(1)
1135 raise ExternalError("No minSdkVersion returned by aapt")
1136
1137
1138def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001139 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001140
Tao Baof47bf0f2018-03-21 23:28:51 -07001141 If minSdkVersion is set to a codename, it is translated to a number using the
1142 provided map.
1143
1144 Args:
1145 apk_name: The APK filename.
1146
1147 Returns:
1148 The parsed SDK version number.
1149
1150 Raises:
1151 ExternalError: On failing to get the min SDK version number.
1152 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001153 version = GetMinSdkVersion(apk_name)
1154 try:
1155 return int(version)
1156 except ValueError:
1157 # Not a decimal number. Codename?
1158 if version in codename_to_api_level_map:
1159 return codename_to_api_level_map[version]
1160 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001161 raise ExternalError(
1162 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1163 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001164
1165
1166def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001167 codename_to_api_level_map=None, whole_file=False,
1168 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001169 """Sign the input_name zip/jar/apk, producing output_name. Use the
1170 given key and password (the latter may be None if the key does not
1171 have a password.
1172
Doug Zongker951495f2009-08-14 12:44:19 -07001173 If whole_file is true, use the "-w" option to SignApk to embed a
1174 signature that covers the whole file in the archive comment of the
1175 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001176
1177 min_api_level is the API Level (int) of the oldest platform this file may end
1178 up on. If not specified for an APK, the API Level is obtained by interpreting
1179 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1180
1181 codename_to_api_level_map is needed to translate the codename which may be
1182 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001183
1184 Caller may optionally specify extra args to be passed to SignApk, which
1185 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001186 """
Tao Bao76def242017-11-21 09:25:31 -08001187 if codename_to_api_level_map is None:
1188 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001189 if extra_signapk_args is None:
1190 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001191
Alex Klyubin9667b182015-12-10 13:38:50 -08001192 java_library_path = os.path.join(
1193 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1194
Tao Baoe95540e2016-11-08 12:08:53 -08001195 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1196 ["-Djava.library.path=" + java_library_path,
1197 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001198 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001199 if whole_file:
1200 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001201
1202 min_sdk_version = min_api_level
1203 if min_sdk_version is None:
1204 if not whole_file:
1205 min_sdk_version = GetMinSdkVersionInt(
1206 input_name, codename_to_api_level_map)
1207 if min_sdk_version is not None:
1208 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1209
T.R. Fullhart37e10522013-03-18 10:31:26 -07001210 cmd.extend([key + OPTIONS.public_key_suffix,
1211 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001212 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001213
Tao Bao73dd4f42018-10-04 16:25:33 -07001214 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001215 if password is not None:
1216 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001217 stdoutdata, _ = proc.communicate(password)
1218 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001219 raise ExternalError(
1220 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001221 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001222
Doug Zongkereef39442009-04-02 12:14:19 -07001223
Doug Zongker37974732010-09-16 17:44:38 -07001224def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001225 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001226
Tao Bao9dd909e2017-11-14 11:27:32 -08001227 For non-AVB images, raise exception if the data is too big. Print a warning
1228 if the data is nearing the maximum size.
1229
1230 For AVB images, the actual image size should be identical to the limit.
1231
1232 Args:
1233 data: A string that contains all the data for the partition.
1234 target: The partition name. The ".img" suffix is optional.
1235 info_dict: The dict to be looked up for relevant info.
1236 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001237 if target.endswith(".img"):
1238 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001239 mount_point = "/" + target
1240
Ying Wangf8824af2014-06-03 14:07:27 -07001241 fs_type = None
1242 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001243 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001244 if mount_point == "/userdata":
1245 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001246 p = info_dict["fstab"][mount_point]
1247 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001248 device = p.device
1249 if "/" in device:
1250 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001251 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001252 if not fs_type or not limit:
1253 return
Doug Zongkereef39442009-04-02 12:14:19 -07001254
Andrew Boie0f9aec82012-02-14 09:32:52 -08001255 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001256 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1257 # path.
1258 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1259 if size != limit:
1260 raise ExternalError(
1261 "Mismatching image size for %s: expected %d actual %d" % (
1262 target, limit, size))
1263 else:
1264 pct = float(size) * 100.0 / limit
1265 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1266 if pct >= 99.0:
1267 raise ExternalError(msg)
1268 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001269 logger.warning("\n WARNING: %s\n", msg)
1270 else:
1271 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001272
1273
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001274def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001275 """Parses the APK certs info from a given target-files zip.
1276
1277 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1278 tuple with the following elements: (1) a dictionary that maps packages to
1279 certs (based on the "certificate" and "private_key" attributes in the file;
1280 (2) a string representing the extension of compressed APKs in the target files
1281 (e.g ".gz", ".bro").
1282
1283 Args:
1284 tf_zip: The input target_files ZipFile (already open).
1285
1286 Returns:
1287 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1288 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1289 no compressed APKs.
1290 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001291 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001292 compressed_extension = None
1293
Tao Bao0f990332017-09-08 19:02:54 -07001294 # META/apkcerts.txt contains the info for _all_ the packages known at build
1295 # time. Filter out the ones that are not installed.
1296 installed_files = set()
1297 for name in tf_zip.namelist():
1298 basename = os.path.basename(name)
1299 if basename:
1300 installed_files.add(basename)
1301
Tao Baoda30cfa2017-12-01 16:19:46 -08001302 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001303 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001304 if not line:
1305 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001306 m = re.match(
1307 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1308 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1309 line)
1310 if not m:
1311 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001312
Tao Bao818ddf52018-01-05 11:17:34 -08001313 matches = m.groupdict()
1314 cert = matches["CERT"]
1315 privkey = matches["PRIVKEY"]
1316 name = matches["NAME"]
1317 this_compressed_extension = matches["COMPRESSED"]
1318
1319 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1320 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1321 if cert in SPECIAL_CERT_STRINGS and not privkey:
1322 certmap[name] = cert
1323 elif (cert.endswith(OPTIONS.public_key_suffix) and
1324 privkey.endswith(OPTIONS.private_key_suffix) and
1325 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1326 certmap[name] = cert[:-public_key_suffix_len]
1327 else:
1328 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1329
1330 if not this_compressed_extension:
1331 continue
1332
1333 # Only count the installed files.
1334 filename = name + '.' + this_compressed_extension
1335 if filename not in installed_files:
1336 continue
1337
1338 # Make sure that all the values in the compression map have the same
1339 # extension. We don't support multiple compression methods in the same
1340 # system image.
1341 if compressed_extension:
1342 if this_compressed_extension != compressed_extension:
1343 raise ValueError(
1344 "Multiple compressed extensions: {} vs {}".format(
1345 compressed_extension, this_compressed_extension))
1346 else:
1347 compressed_extension = this_compressed_extension
1348
1349 return (certmap,
1350 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001351
1352
Doug Zongkereef39442009-04-02 12:14:19 -07001353COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001354Global options
1355
1356 -p (--path) <dir>
1357 Prepend <dir>/bin to the list of places to search for binaries run by this
1358 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001359
Doug Zongker05d3dea2009-06-22 11:32:31 -07001360 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001361 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001362
Tao Bao30df8b42018-04-23 15:32:53 -07001363 -x (--extra) <key=value>
1364 Add a key/value pair to the 'extras' dict, which device-specific extension
1365 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001366
Doug Zongkereef39442009-04-02 12:14:19 -07001367 -v (--verbose)
1368 Show command lines being executed.
1369
1370 -h (--help)
1371 Display this usage message and exit.
1372"""
1373
1374def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001375 print(docstring.rstrip("\n"))
1376 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001377
1378
1379def ParseOptions(argv,
1380 docstring,
1381 extra_opts="", extra_long_opts=(),
1382 extra_option_handler=None):
1383 """Parse the options in argv and return any arguments that aren't
1384 flags. docstring is the calling module's docstring, to be displayed
1385 for errors and -h. extra_opts and extra_long_opts are for flags
1386 defined by the caller, which are processed by passing them to
1387 extra_option_handler."""
1388
1389 try:
1390 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001391 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001392 ["help", "verbose", "path=", "signapk_path=",
1393 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001394 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001395 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1396 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001397 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001398 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001399 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001400 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001401 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001402 sys.exit(2)
1403
Doug Zongkereef39442009-04-02 12:14:19 -07001404 for o, a in opts:
1405 if o in ("-h", "--help"):
1406 Usage(docstring)
1407 sys.exit()
1408 elif o in ("-v", "--verbose"):
1409 OPTIONS.verbose = True
1410 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001411 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001412 elif o in ("--signapk_path",):
1413 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001414 elif o in ("--signapk_shared_library_path",):
1415 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001416 elif o in ("--extra_signapk_args",):
1417 OPTIONS.extra_signapk_args = shlex.split(a)
1418 elif o in ("--java_path",):
1419 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001420 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001421 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001422 elif o in ("--public_key_suffix",):
1423 OPTIONS.public_key_suffix = a
1424 elif o in ("--private_key_suffix",):
1425 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001426 elif o in ("--boot_signer_path",):
1427 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001428 elif o in ("--boot_signer_args",):
1429 OPTIONS.boot_signer_args = shlex.split(a)
1430 elif o in ("--verity_signer_path",):
1431 OPTIONS.verity_signer_path = a
1432 elif o in ("--verity_signer_args",):
1433 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001434 elif o in ("-s", "--device_specific"):
1435 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001436 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001437 key, value = a.split("=", 1)
1438 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001439 else:
1440 if extra_option_handler is None or not extra_option_handler(o, a):
1441 assert False, "unknown option \"%s\"" % (o,)
1442
Doug Zongker85448772014-09-09 14:59:20 -07001443 if OPTIONS.search_path:
1444 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1445 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001446
1447 return args
1448
1449
Tao Bao4c851b12016-09-19 13:54:38 -07001450def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001451 """Make a temp file and add it to the list of things to be deleted
1452 when Cleanup() is called. Return the filename."""
1453 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1454 os.close(fd)
1455 OPTIONS.tempfiles.append(fn)
1456 return fn
1457
1458
Tao Bao1c830bf2017-12-25 10:43:47 -08001459def MakeTempDir(prefix='tmp', suffix=''):
1460 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1461
1462 Returns:
1463 The absolute pathname of the new directory.
1464 """
1465 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1466 OPTIONS.tempfiles.append(dir_name)
1467 return dir_name
1468
1469
Doug Zongkereef39442009-04-02 12:14:19 -07001470def Cleanup():
1471 for i in OPTIONS.tempfiles:
1472 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001473 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001474 else:
1475 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001476 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001477
1478
1479class PasswordManager(object):
1480 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001481 self.editor = os.getenv("EDITOR")
1482 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001483
1484 def GetPasswords(self, items):
1485 """Get passwords corresponding to each string in 'items',
1486 returning a dict. (The dict may have keys in addition to the
1487 values in 'items'.)
1488
1489 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1490 user edit that file to add more needed passwords. If no editor is
1491 available, or $ANDROID_PW_FILE isn't define, prompts the user
1492 interactively in the ordinary way.
1493 """
1494
1495 current = self.ReadFile()
1496
1497 first = True
1498 while True:
1499 missing = []
1500 for i in items:
1501 if i not in current or not current[i]:
1502 missing.append(i)
1503 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001504 if not missing:
1505 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001506
1507 for i in missing:
1508 current[i] = ""
1509
1510 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001511 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001512 if sys.version_info[0] >= 3:
1513 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001514 answer = raw_input("try to edit again? [y]> ").strip()
1515 if answer and answer[0] not in 'yY':
1516 raise RuntimeError("key passwords unavailable")
1517 first = False
1518
1519 current = self.UpdateAndReadFile(current)
1520
Dan Albert8b72aef2015-03-23 19:13:21 -07001521 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001522 """Prompt the user to enter a value (password) for each key in
1523 'current' whose value is fales. Returns a new dict with all the
1524 values.
1525 """
1526 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001527 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001528 if v:
1529 result[k] = v
1530 else:
1531 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001532 result[k] = getpass.getpass(
1533 "Enter password for %s key> " % k).strip()
1534 if result[k]:
1535 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001536 return result
1537
1538 def UpdateAndReadFile(self, current):
1539 if not self.editor or not self.pwfile:
1540 return self.PromptResult(current)
1541
1542 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001543 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001544 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1545 f.write("# (Additional spaces are harmless.)\n\n")
1546
1547 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001548 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001549 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001550 f.write("[[[ %s ]]] %s\n" % (v, k))
1551 if not v and first_line is None:
1552 # position cursor on first line with no password.
1553 first_line = i + 4
1554 f.close()
1555
Tao Bao986ee862018-10-04 15:46:16 -07001556 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001557
1558 return self.ReadFile()
1559
1560 def ReadFile(self):
1561 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001562 if self.pwfile is None:
1563 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001564 try:
1565 f = open(self.pwfile, "r")
1566 for line in f:
1567 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001568 if not line or line[0] == '#':
1569 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001570 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1571 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001572 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001573 else:
1574 result[m.group(2)] = m.group(1)
1575 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001576 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001577 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001578 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001579 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001580
1581
Dan Albert8e0178d2015-01-27 15:53:15 -08001582def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1583 compress_type=None):
1584 import datetime
1585
1586 # http://b/18015246
1587 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1588 # for files larger than 2GiB. We can work around this by adjusting their
1589 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1590 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1591 # it isn't clear to me exactly what circumstances cause this).
1592 # `zipfile.write()` must be used directly to work around this.
1593 #
1594 # This mess can be avoided if we port to python3.
1595 saved_zip64_limit = zipfile.ZIP64_LIMIT
1596 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1597
1598 if compress_type is None:
1599 compress_type = zip_file.compression
1600 if arcname is None:
1601 arcname = filename
1602
1603 saved_stat = os.stat(filename)
1604
1605 try:
1606 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1607 # file to be zipped and reset it when we're done.
1608 os.chmod(filename, perms)
1609
1610 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001611 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1612 # intentional. zip stores datetimes in local time without a time zone
1613 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1614 # in the zip archive.
1615 local_epoch = datetime.datetime.fromtimestamp(0)
1616 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001617 os.utime(filename, (timestamp, timestamp))
1618
1619 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1620 finally:
1621 os.chmod(filename, saved_stat.st_mode)
1622 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1623 zipfile.ZIP64_LIMIT = saved_zip64_limit
1624
1625
Tao Bao58c1b962015-05-20 09:32:18 -07001626def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001627 compress_type=None):
1628 """Wrap zipfile.writestr() function to work around the zip64 limit.
1629
1630 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1631 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1632 when calling crc32(bytes).
1633
1634 But it still works fine to write a shorter string into a large zip file.
1635 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1636 when we know the string won't be too long.
1637 """
1638
1639 saved_zip64_limit = zipfile.ZIP64_LIMIT
1640 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1641
1642 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1643 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001644 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001645 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001646 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001647 else:
Tao Baof3282b42015-04-01 11:21:55 -07001648 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001649 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1650 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1651 # such a case (since
1652 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1653 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1654 # permission bits. We follow the logic in Python 3 to get consistent
1655 # behavior between using the two versions.
1656 if not zinfo.external_attr:
1657 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001658
1659 # If compress_type is given, it overrides the value in zinfo.
1660 if compress_type is not None:
1661 zinfo.compress_type = compress_type
1662
Tao Bao58c1b962015-05-20 09:32:18 -07001663 # If perms is given, it has a priority.
1664 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001665 # If perms doesn't set the file type, mark it as a regular file.
1666 if perms & 0o770000 == 0:
1667 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001668 zinfo.external_attr = perms << 16
1669
Tao Baof3282b42015-04-01 11:21:55 -07001670 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001671 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1672
Dan Albert8b72aef2015-03-23 19:13:21 -07001673 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001674 zipfile.ZIP64_LIMIT = saved_zip64_limit
1675
1676
Tao Bao89d7ab22017-12-14 17:05:33 -08001677def ZipDelete(zip_filename, entries):
1678 """Deletes entries from a ZIP file.
1679
1680 Since deleting entries from a ZIP file is not supported, it shells out to
1681 'zip -d'.
1682
1683 Args:
1684 zip_filename: The name of the ZIP file.
1685 entries: The name of the entry, or the list of names to be deleted.
1686
1687 Raises:
1688 AssertionError: In case of non-zero return from 'zip'.
1689 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001690 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001691 entries = [entries]
1692 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001693 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001694
1695
Tao Baof3282b42015-04-01 11:21:55 -07001696def ZipClose(zip_file):
1697 # http://b/18015246
1698 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1699 # central directory.
1700 saved_zip64_limit = zipfile.ZIP64_LIMIT
1701 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1702
1703 zip_file.close()
1704
1705 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001706
1707
1708class DeviceSpecificParams(object):
1709 module = None
1710 def __init__(self, **kwargs):
1711 """Keyword arguments to the constructor become attributes of this
1712 object, which is passed to all functions in the device-specific
1713 module."""
Tao Bao38884282019-07-10 22:20:56 -07001714 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07001715 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001716 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001717
1718 if self.module is None:
1719 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001720 if not path:
1721 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001722 try:
1723 if os.path.isdir(path):
1724 info = imp.find_module("releasetools", [path])
1725 else:
1726 d, f = os.path.split(path)
1727 b, x = os.path.splitext(f)
1728 if x == ".py":
1729 f = b
1730 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001731 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001732 self.module = imp.load_module("device_specific", *info)
1733 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001734 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001735
1736 def _DoCall(self, function_name, *args, **kwargs):
1737 """Call the named function in the device-specific module, passing
1738 the given args and kwargs. The first argument to the call will be
1739 the DeviceSpecific object itself. If there is no module, or the
1740 module does not define the function, return the value of the
1741 'default' kwarg (which itself defaults to None)."""
1742 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001743 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001744 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1745
1746 def FullOTA_Assertions(self):
1747 """Called after emitting the block of assertions at the top of a
1748 full OTA package. Implementations can add whatever additional
1749 assertions they like."""
1750 return self._DoCall("FullOTA_Assertions")
1751
Doug Zongkere5ff5902012-01-17 10:55:37 -08001752 def FullOTA_InstallBegin(self):
1753 """Called at the start of full OTA installation."""
1754 return self._DoCall("FullOTA_InstallBegin")
1755
Yifan Hong10c530d2018-12-27 17:34:18 -08001756 def FullOTA_GetBlockDifferences(self):
1757 """Called during full OTA installation and verification.
1758 Implementation should return a list of BlockDifference objects describing
1759 the update on each additional partitions.
1760 """
1761 return self._DoCall("FullOTA_GetBlockDifferences")
1762
Doug Zongker05d3dea2009-06-22 11:32:31 -07001763 def FullOTA_InstallEnd(self):
1764 """Called at the end of full OTA installation; typically this is
1765 used to install the image for the device's baseband processor."""
1766 return self._DoCall("FullOTA_InstallEnd")
1767
1768 def IncrementalOTA_Assertions(self):
1769 """Called after emitting the block of assertions at the top of an
1770 incremental OTA package. Implementations can add whatever
1771 additional assertions they like."""
1772 return self._DoCall("IncrementalOTA_Assertions")
1773
Doug Zongkere5ff5902012-01-17 10:55:37 -08001774 def IncrementalOTA_VerifyBegin(self):
1775 """Called at the start of the verification phase of incremental
1776 OTA installation; additional checks can be placed here to abort
1777 the script before any changes are made."""
1778 return self._DoCall("IncrementalOTA_VerifyBegin")
1779
Doug Zongker05d3dea2009-06-22 11:32:31 -07001780 def IncrementalOTA_VerifyEnd(self):
1781 """Called at the end of the verification phase of incremental OTA
1782 installation; additional checks can be placed here to abort the
1783 script before any changes are made."""
1784 return self._DoCall("IncrementalOTA_VerifyEnd")
1785
Doug Zongkere5ff5902012-01-17 10:55:37 -08001786 def IncrementalOTA_InstallBegin(self):
1787 """Called at the start of incremental OTA installation (after
1788 verification is complete)."""
1789 return self._DoCall("IncrementalOTA_InstallBegin")
1790
Yifan Hong10c530d2018-12-27 17:34:18 -08001791 def IncrementalOTA_GetBlockDifferences(self):
1792 """Called during incremental OTA installation and verification.
1793 Implementation should return a list of BlockDifference objects describing
1794 the update on each additional partitions.
1795 """
1796 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1797
Doug Zongker05d3dea2009-06-22 11:32:31 -07001798 def IncrementalOTA_InstallEnd(self):
1799 """Called at the end of incremental OTA installation; typically
1800 this is used to install the image for the device's baseband
1801 processor."""
1802 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001803
Tao Bao9bc6bb22015-11-09 16:58:28 -08001804 def VerifyOTA_Assertions(self):
1805 return self._DoCall("VerifyOTA_Assertions")
1806
Tao Bao76def242017-11-21 09:25:31 -08001807
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001808class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001809 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001810 self.name = name
1811 self.data = data
1812 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001813 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001814 self.sha1 = sha1(data).hexdigest()
1815
1816 @classmethod
1817 def FromLocalFile(cls, name, diskname):
1818 f = open(diskname, "rb")
1819 data = f.read()
1820 f.close()
1821 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001822
1823 def WriteToTemp(self):
1824 t = tempfile.NamedTemporaryFile()
1825 t.write(self.data)
1826 t.flush()
1827 return t
1828
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001829 def WriteToDir(self, d):
1830 with open(os.path.join(d, self.name), "wb") as fp:
1831 fp.write(self.data)
1832
Geremy Condra36bd3652014-02-06 19:45:10 -08001833 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001834 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001835
Tao Bao76def242017-11-21 09:25:31 -08001836
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001837DIFF_PROGRAM_BY_EXT = {
1838 ".gz" : "imgdiff",
1839 ".zip" : ["imgdiff", "-z"],
1840 ".jar" : ["imgdiff", "-z"],
1841 ".apk" : ["imgdiff", "-z"],
1842 ".img" : "imgdiff",
1843 }
1844
Tao Bao76def242017-11-21 09:25:31 -08001845
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001846class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001847 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001848 self.tf = tf
1849 self.sf = sf
1850 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001851 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001852
1853 def ComputePatch(self):
1854 """Compute the patch (as a string of data) needed to turn sf into
1855 tf. Returns the same tuple as GetPatch()."""
1856
1857 tf = self.tf
1858 sf = self.sf
1859
Doug Zongker24cd2802012-08-14 16:36:15 -07001860 if self.diff_program:
1861 diff_program = self.diff_program
1862 else:
1863 ext = os.path.splitext(tf.name)[1]
1864 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001865
1866 ttemp = tf.WriteToTemp()
1867 stemp = sf.WriteToTemp()
1868
1869 ext = os.path.splitext(tf.name)[1]
1870
1871 try:
1872 ptemp = tempfile.NamedTemporaryFile()
1873 if isinstance(diff_program, list):
1874 cmd = copy.copy(diff_program)
1875 else:
1876 cmd = [diff_program]
1877 cmd.append(stemp.name)
1878 cmd.append(ttemp.name)
1879 cmd.append(ptemp.name)
1880 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001881 err = []
1882 def run():
1883 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001884 if e:
1885 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001886 th = threading.Thread(target=run)
1887 th.start()
1888 th.join(timeout=300) # 5 mins
1889 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001890 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001891 p.terminate()
1892 th.join(5)
1893 if th.is_alive():
1894 p.kill()
1895 th.join()
1896
Tianjie Xua2a9f992018-01-05 15:15:54 -08001897 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001898 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001899 self.patch = None
1900 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001901 diff = ptemp.read()
1902 finally:
1903 ptemp.close()
1904 stemp.close()
1905 ttemp.close()
1906
1907 self.patch = diff
1908 return self.tf, self.sf, self.patch
1909
1910
1911 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001912 """Returns a tuple of (target_file, source_file, patch_data).
1913
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001914 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001915 computing the patch failed.
1916 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001917 return self.tf, self.sf, self.patch
1918
1919
1920def ComputeDifferences(diffs):
1921 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001922 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001923
1924 # Do the largest files first, to try and reduce the long-pole effect.
1925 by_size = [(i.tf.size, i) for i in diffs]
1926 by_size.sort(reverse=True)
1927 by_size = [i[1] for i in by_size]
1928
1929 lock = threading.Lock()
1930 diff_iter = iter(by_size) # accessed under lock
1931
1932 def worker():
1933 try:
1934 lock.acquire()
1935 for d in diff_iter:
1936 lock.release()
1937 start = time.time()
1938 d.ComputePatch()
1939 dur = time.time() - start
1940 lock.acquire()
1941
1942 tf, sf, patch = d.GetPatch()
1943 if sf.name == tf.name:
1944 name = tf.name
1945 else:
1946 name = "%s (%s)" % (tf.name, sf.name)
1947 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001948 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001949 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001950 logger.info(
1951 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1952 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001953 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001954 except Exception:
1955 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001956 raise
1957
1958 # start worker threads; wait for them all to finish.
1959 threads = [threading.Thread(target=worker)
1960 for i in range(OPTIONS.worker_threads)]
1961 for th in threads:
1962 th.start()
1963 while threads:
1964 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001965
1966
Dan Albert8b72aef2015-03-23 19:13:21 -07001967class BlockDifference(object):
1968 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001969 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001970 self.tgt = tgt
1971 self.src = src
1972 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001973 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001974 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001975
Tao Baodd2a5892015-03-12 12:32:37 -07001976 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001977 version = max(
1978 int(i) for i in
1979 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001980 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001981 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001982
Tianjie Xu41976c72019-07-03 13:57:01 -07001983 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
1984 version=self.version,
1985 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001986 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001987 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001988 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001989 self.touched_src_ranges = b.touched_src_ranges
1990 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001991
Yifan Hong10c530d2018-12-27 17:34:18 -08001992 # On devices with dynamic partitions, for new partitions,
1993 # src is None but OPTIONS.source_info_dict is not.
1994 if OPTIONS.source_info_dict is None:
1995 is_dynamic_build = OPTIONS.info_dict.get(
1996 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001997 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001998 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001999 is_dynamic_build = OPTIONS.source_info_dict.get(
2000 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002001 is_dynamic_source = partition in shlex.split(
2002 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002003
Yifan Hongbb2658d2019-01-25 12:30:58 -08002004 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002005 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2006
Yifan Hongbb2658d2019-01-25 12:30:58 -08002007 # For dynamic partitions builds, check partition list in both source
2008 # and target build because new partitions may be added, and existing
2009 # partitions may be removed.
2010 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2011
Yifan Hong10c530d2018-12-27 17:34:18 -08002012 if is_dynamic:
2013 self.device = 'map_partition("%s")' % partition
2014 else:
2015 if OPTIONS.source_info_dict is None:
2016 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2017 else:
2018 _, device_path = GetTypeAndDevice("/" + partition,
2019 OPTIONS.source_info_dict)
2020 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002021
Tao Baod8d14be2016-02-04 14:26:02 -08002022 @property
2023 def required_cache(self):
2024 return self._required_cache
2025
Tao Bao76def242017-11-21 09:25:31 -08002026 def WriteScript(self, script, output_zip, progress=None,
2027 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002028 if not self.src:
2029 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002030 script.Print("Patching %s image unconditionally..." % (self.partition,))
2031 else:
2032 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002033
Dan Albert8b72aef2015-03-23 19:13:21 -07002034 if progress:
2035 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002036 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002037
2038 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002039 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002040
Tao Bao9bc6bb22015-11-09 16:58:28 -08002041 def WriteStrictVerifyScript(self, script):
2042 """Verify all the blocks in the care_map, including clobbered blocks.
2043
2044 This differs from the WriteVerifyScript() function: a) it prints different
2045 error messages; b) it doesn't allow half-way updated images to pass the
2046 verification."""
2047
2048 partition = self.partition
2049 script.Print("Verifying %s..." % (partition,))
2050 ranges = self.tgt.care_map
2051 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002052 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002053 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2054 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002055 self.device, ranges_str,
2056 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002057 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002058 script.AppendExtra("")
2059
Tao Baod522bdc2016-04-12 15:53:16 -07002060 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002061 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002062
2063 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002064 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002065 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002066
2067 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002068 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002069 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002070 ranges = self.touched_src_ranges
2071 expected_sha1 = self.touched_src_sha1
2072 else:
2073 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2074 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002075
2076 # No blocks to be checked, skipping.
2077 if not ranges:
2078 return
2079
Tao Bao5ece99d2015-05-12 11:42:31 -07002080 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002081 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002082 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002083 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2084 '"%s.patch.dat")) then' % (
2085 self.device, ranges_str, expected_sha1,
2086 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002087 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002088 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002089
Tianjie Xufc3422a2015-12-15 11:53:59 -08002090 if self.version >= 4:
2091
2092 # Bug: 21124327
2093 # When generating incrementals for the system and vendor partitions in
2094 # version 4 or newer, explicitly check the first block (which contains
2095 # the superblock) of the partition to see if it's what we expect. If
2096 # this check fails, give an explicit log message about the partition
2097 # having been remounted R/W (the most likely explanation).
2098 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002099 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002100
2101 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002102 if partition == "system":
2103 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2104 else:
2105 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002106 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002107 'ifelse (block_image_recover({device}, "{ranges}") && '
2108 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002109 'package_extract_file("{partition}.transfer.list"), '
2110 '"{partition}.new.dat", "{partition}.patch.dat"), '
2111 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002112 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002113 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002114 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002115
Tao Baodd2a5892015-03-12 12:32:37 -07002116 # Abort the OTA update. Note that the incremental OTA cannot be applied
2117 # even if it may match the checksum of the target partition.
2118 # a) If version < 3, operations like move and erase will make changes
2119 # unconditionally and damage the partition.
2120 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002121 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002122 if partition == "system":
2123 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2124 else:
2125 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2126 script.AppendExtra((
2127 'abort("E%d: %s partition has unexpected contents");\n'
2128 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002129
Yifan Hong10c530d2018-12-27 17:34:18 -08002130 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002131 partition = self.partition
2132 script.Print('Verifying the updated %s image...' % (partition,))
2133 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2134 ranges = self.tgt.care_map
2135 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002136 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002137 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002138 self.device, ranges_str,
2139 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002140
2141 # Bug: 20881595
2142 # Verify that extended blocks are really zeroed out.
2143 if self.tgt.extended:
2144 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002145 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002146 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002147 self.device, ranges_str,
2148 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002149 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002150 if partition == "system":
2151 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2152 else:
2153 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002154 script.AppendExtra(
2155 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002156 ' abort("E%d: %s partition has unexpected non-zero contents after '
2157 'OTA update");\n'
2158 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002159 else:
2160 script.Print('Verified the updated %s image.' % (partition,))
2161
Tianjie Xu209db462016-05-24 17:34:52 -07002162 if partition == "system":
2163 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2164 else:
2165 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2166
Tao Bao5fcaaef2015-06-01 13:40:49 -07002167 script.AppendExtra(
2168 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002169 ' abort("E%d: %s partition has unexpected contents after OTA '
2170 'update");\n'
2171 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002172
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002173 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002174 ZipWrite(output_zip,
2175 '{}.transfer.list'.format(self.path),
2176 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002177
Tao Bao76def242017-11-21 09:25:31 -08002178 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2179 # its size. Quailty 9 almost triples the compression time but doesn't
2180 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002181 # zip | brotli(quality 6) | brotli(quality 9)
2182 # compressed_size: 942M | 869M (~8% reduced) | 854M
2183 # compression_time: 75s | 265s | 719s
2184 # decompression_time: 15s | 25s | 25s
2185
2186 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002187 brotli_cmd = ['brotli', '--quality=6',
2188 '--output={}.new.dat.br'.format(self.path),
2189 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002190 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002191 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002192
2193 new_data_name = '{}.new.dat.br'.format(self.partition)
2194 ZipWrite(output_zip,
2195 '{}.new.dat.br'.format(self.path),
2196 new_data_name,
2197 compress_type=zipfile.ZIP_STORED)
2198 else:
2199 new_data_name = '{}.new.dat'.format(self.partition)
2200 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2201
Dan Albert8e0178d2015-01-27 15:53:15 -08002202 ZipWrite(output_zip,
2203 '{}.patch.dat'.format(self.path),
2204 '{}.patch.dat'.format(self.partition),
2205 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002206
Tianjie Xu209db462016-05-24 17:34:52 -07002207 if self.partition == "system":
2208 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2209 else:
2210 code = ErrorCode.VENDOR_UPDATE_FAILURE
2211
Yifan Hong10c530d2018-12-27 17:34:18 -08002212 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002213 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002214 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002215 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002216 device=self.device, partition=self.partition,
2217 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002218 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002219
Dan Albert8b72aef2015-03-23 19:13:21 -07002220 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002221 data = source.ReadRangeSet(ranges)
2222 ctx = sha1()
2223
2224 for p in data:
2225 ctx.update(p)
2226
2227 return ctx.hexdigest()
2228
Tao Baoe9b61912015-07-09 17:37:49 -07002229 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2230 """Return the hash value for all zero blocks."""
2231 zero_block = '\x00' * 4096
2232 ctx = sha1()
2233 for _ in range(num_blocks):
2234 ctx.update(zero_block)
2235
2236 return ctx.hexdigest()
2237
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002238
Tianjie Xu41976c72019-07-03 13:57:01 -07002239# Expose these two classes to support vendor-specific scripts
2240DataImage = images.DataImage
2241EmptyImage = images.EmptyImage
2242
Tao Bao76def242017-11-21 09:25:31 -08002243
Doug Zongker96a57e72010-09-26 14:57:41 -07002244# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002245PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002246 "ext4": "EMMC",
2247 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002248 "f2fs": "EMMC",
2249 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002250}
Doug Zongker96a57e72010-09-26 14:57:41 -07002251
Tao Bao76def242017-11-21 09:25:31 -08002252
Doug Zongker96a57e72010-09-26 14:57:41 -07002253def GetTypeAndDevice(mount_point, info):
2254 fstab = info["fstab"]
2255 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002256 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2257 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002258 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002259 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002260
2261
2262def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002263 """Parses and converts a PEM-encoded certificate into DER-encoded.
2264
2265 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2266
2267 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002268 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002269 """
2270 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002271 save = False
2272 for line in data.split("\n"):
2273 if "--END CERTIFICATE--" in line:
2274 break
2275 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002276 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002277 if "--BEGIN CERTIFICATE--" in line:
2278 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002279 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002280 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002281
Tao Bao04e1f012018-02-04 12:13:35 -08002282
2283def ExtractPublicKey(cert):
2284 """Extracts the public key (PEM-encoded) from the given certificate file.
2285
2286 Args:
2287 cert: The certificate filename.
2288
2289 Returns:
2290 The public key string.
2291
2292 Raises:
2293 AssertionError: On non-zero return from 'openssl'.
2294 """
2295 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2296 # While openssl 1.1 writes the key into the given filename followed by '-out',
2297 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2298 # stdout instead.
2299 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2300 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2301 pubkey, stderrdata = proc.communicate()
2302 assert proc.returncode == 0, \
2303 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2304 return pubkey
2305
2306
Tao Bao1ac886e2019-06-26 11:58:22 -07002307def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002308 """Extracts the AVB public key from the given public or private key.
2309
2310 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002311 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002312 key: The input key file, which should be PEM-encoded public or private key.
2313
2314 Returns:
2315 The path to the extracted AVB public key file.
2316 """
2317 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2318 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002319 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002320 return output
2321
2322
Doug Zongker412c02f2014-02-13 10:58:24 -08002323def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2324 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002325 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002326
Tao Bao6d5d6232018-03-09 17:04:42 -08002327 Most of the space in the boot and recovery images is just the kernel, which is
2328 identical for the two, so the resulting patch should be efficient. Add it to
2329 the output zip, along with a shell script that is run from init.rc on first
2330 boot to actually do the patching and install the new recovery image.
2331
2332 Args:
2333 input_dir: The top-level input directory of the target-files.zip.
2334 output_sink: The callback function that writes the result.
2335 recovery_img: File object for the recovery image.
2336 boot_img: File objects for the boot image.
2337 info_dict: A dict returned by common.LoadInfoDict() on the input
2338 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002339 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002340 if info_dict is None:
2341 info_dict = OPTIONS.info_dict
2342
Tao Bao6d5d6232018-03-09 17:04:42 -08002343 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002344
Tao Baof2cffbd2015-07-22 12:33:18 -07002345 if full_recovery_image:
2346 output_sink("etc/recovery.img", recovery_img.data)
2347
2348 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002349 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002350 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002351 # With system-root-image, boot and recovery images will have mismatching
2352 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2353 # to handle such a case.
2354 if system_root_image:
2355 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002356 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002357 assert not os.path.exists(path)
2358 else:
2359 diff_program = ["imgdiff"]
2360 if os.path.exists(path):
2361 diff_program.append("-b")
2362 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002363 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002364 else:
2365 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002366
2367 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2368 _, _, patch = d.ComputePatch()
2369 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002370
Dan Albertebb19aa2015-03-27 19:11:53 -07002371 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002372 # The following GetTypeAndDevice()s need to use the path in the target
2373 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002374 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2375 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2376 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002377 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002378
Tao Baof2cffbd2015-07-22 12:33:18 -07002379 if full_recovery_image:
2380 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002381if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2382 applypatch \\
2383 --flash /system/etc/recovery.img \\
2384 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2385 log -t recovery "Installing new recovery image: succeeded" || \\
2386 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002387else
2388 log -t recovery "Recovery image already installed"
2389fi
2390""" % {'type': recovery_type,
2391 'device': recovery_device,
2392 'sha1': recovery_img.sha1,
2393 'size': recovery_img.size}
2394 else:
2395 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002396if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2397 applypatch %(bonus_args)s \\
2398 --patch /system/recovery-from-boot.p \\
2399 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2400 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2401 log -t recovery "Installing new recovery image: succeeded" || \\
2402 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002403else
2404 log -t recovery "Recovery image already installed"
2405fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002406""" % {'boot_size': boot_img.size,
2407 'boot_sha1': boot_img.sha1,
2408 'recovery_size': recovery_img.size,
2409 'recovery_sha1': recovery_img.sha1,
2410 'boot_type': boot_type,
2411 'boot_device': boot_device,
2412 'recovery_type': recovery_type,
2413 'recovery_device': recovery_device,
2414 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002415
2416 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002417 # in the L release.
2418 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002419
Tao Bao32fcdab2018-10-12 10:30:39 -07002420 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002421
Tao Baoda30cfa2017-12-01 16:19:46 -08002422 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002423
2424
2425class DynamicPartitionUpdate(object):
2426 def __init__(self, src_group=None, tgt_group=None, progress=None,
2427 block_difference=None):
2428 self.src_group = src_group
2429 self.tgt_group = tgt_group
2430 self.progress = progress
2431 self.block_difference = block_difference
2432
2433 @property
2434 def src_size(self):
2435 if not self.block_difference:
2436 return 0
2437 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2438
2439 @property
2440 def tgt_size(self):
2441 if not self.block_difference:
2442 return 0
2443 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2444
2445 @staticmethod
2446 def _GetSparseImageSize(img):
2447 if not img:
2448 return 0
2449 return img.blocksize * img.total_blocks
2450
2451
2452class DynamicGroupUpdate(object):
2453 def __init__(self, src_size=None, tgt_size=None):
2454 # None: group does not exist. 0: no size limits.
2455 self.src_size = src_size
2456 self.tgt_size = tgt_size
2457
2458
2459class DynamicPartitionsDifference(object):
2460 def __init__(self, info_dict, block_diffs, progress_dict=None,
2461 source_info_dict=None):
2462 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002463 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002464
2465 self._remove_all_before_apply = False
2466 if source_info_dict is None:
2467 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002468 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002469
Tao Baof1113e92019-06-18 12:10:14 -07002470 block_diff_dict = collections.OrderedDict(
2471 [(e.partition, e) for e in block_diffs])
2472
Yifan Hong10c530d2018-12-27 17:34:18 -08002473 assert len(block_diff_dict) == len(block_diffs), \
2474 "Duplicated BlockDifference object for {}".format(
2475 [partition for partition, count in
2476 collections.Counter(e.partition for e in block_diffs).items()
2477 if count > 1])
2478
Yifan Hong79997e52019-01-23 16:56:19 -08002479 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002480
2481 for p, block_diff in block_diff_dict.items():
2482 self._partition_updates[p] = DynamicPartitionUpdate()
2483 self._partition_updates[p].block_difference = block_diff
2484
2485 for p, progress in progress_dict.items():
2486 if p in self._partition_updates:
2487 self._partition_updates[p].progress = progress
2488
2489 tgt_groups = shlex.split(info_dict.get(
2490 "super_partition_groups", "").strip())
2491 src_groups = shlex.split(source_info_dict.get(
2492 "super_partition_groups", "").strip())
2493
2494 for g in tgt_groups:
2495 for p in shlex.split(info_dict.get(
2496 "super_%s_partition_list" % g, "").strip()):
2497 assert p in self._partition_updates, \
2498 "{} is in target super_{}_partition_list but no BlockDifference " \
2499 "object is provided.".format(p, g)
2500 self._partition_updates[p].tgt_group = g
2501
2502 for g in src_groups:
2503 for p in shlex.split(source_info_dict.get(
2504 "super_%s_partition_list" % g, "").strip()):
2505 assert p in self._partition_updates, \
2506 "{} is in source super_{}_partition_list but no BlockDifference " \
2507 "object is provided.".format(p, g)
2508 self._partition_updates[p].src_group = g
2509
Yifan Hong45433e42019-01-18 13:55:25 -08002510 target_dynamic_partitions = set(shlex.split(info_dict.get(
2511 "dynamic_partition_list", "").strip()))
2512 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2513 if u.tgt_size)
2514 assert block_diffs_with_target == target_dynamic_partitions, \
2515 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2516 list(target_dynamic_partitions), list(block_diffs_with_target))
2517
2518 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2519 "dynamic_partition_list", "").strip()))
2520 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2521 if u.src_size)
2522 assert block_diffs_with_source == source_dynamic_partitions, \
2523 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2524 list(source_dynamic_partitions), list(block_diffs_with_source))
2525
Yifan Hong10c530d2018-12-27 17:34:18 -08002526 if self._partition_updates:
2527 logger.info("Updating dynamic partitions %s",
2528 self._partition_updates.keys())
2529
Yifan Hong79997e52019-01-23 16:56:19 -08002530 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002531
2532 for g in tgt_groups:
2533 self._group_updates[g] = DynamicGroupUpdate()
2534 self._group_updates[g].tgt_size = int(info_dict.get(
2535 "super_%s_group_size" % g, "0").strip())
2536
2537 for g in src_groups:
2538 if g not in self._group_updates:
2539 self._group_updates[g] = DynamicGroupUpdate()
2540 self._group_updates[g].src_size = int(source_info_dict.get(
2541 "super_%s_group_size" % g, "0").strip())
2542
2543 self._Compute()
2544
2545 def WriteScript(self, script, output_zip, write_verify_script=False):
2546 script.Comment('--- Start patching dynamic partitions ---')
2547 for p, u in self._partition_updates.items():
2548 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2549 script.Comment('Patch partition %s' % p)
2550 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2551 write_verify_script=False)
2552
2553 op_list_path = MakeTempFile()
2554 with open(op_list_path, 'w') as f:
2555 for line in self._op_list:
2556 f.write('{}\n'.format(line))
2557
2558 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2559
2560 script.Comment('Update dynamic partition metadata')
2561 script.AppendExtra('assert(update_dynamic_partitions('
2562 'package_extract_file("dynamic_partitions_op_list")));')
2563
2564 if write_verify_script:
2565 for p, u in self._partition_updates.items():
2566 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2567 u.block_difference.WritePostInstallVerifyScript(script)
2568 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2569
2570 for p, u in self._partition_updates.items():
2571 if u.tgt_size and u.src_size <= u.tgt_size:
2572 script.Comment('Patch partition %s' % p)
2573 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2574 write_verify_script=write_verify_script)
2575 if write_verify_script:
2576 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2577
2578 script.Comment('--- End patching dynamic partitions ---')
2579
2580 def _Compute(self):
2581 self._op_list = list()
2582
2583 def append(line):
2584 self._op_list.append(line)
2585
2586 def comment(line):
2587 self._op_list.append("# %s" % line)
2588
2589 if self._remove_all_before_apply:
2590 comment('Remove all existing dynamic partitions and groups before '
2591 'applying full OTA')
2592 append('remove_all_groups')
2593
2594 for p, u in self._partition_updates.items():
2595 if u.src_group and not u.tgt_group:
2596 append('remove %s' % p)
2597
2598 for p, u in self._partition_updates.items():
2599 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2600 comment('Move partition %s from %s to default' % (p, u.src_group))
2601 append('move %s default' % p)
2602
2603 for p, u in self._partition_updates.items():
2604 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2605 comment('Shrink partition %s from %d to %d' %
2606 (p, u.src_size, u.tgt_size))
2607 append('resize %s %s' % (p, u.tgt_size))
2608
2609 for g, u in self._group_updates.items():
2610 if u.src_size is not None and u.tgt_size is None:
2611 append('remove_group %s' % g)
2612 if (u.src_size is not None and u.tgt_size is not None and
2613 u.src_size > u.tgt_size):
2614 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2615 append('resize_group %s %d' % (g, u.tgt_size))
2616
2617 for g, u in self._group_updates.items():
2618 if u.src_size is None and u.tgt_size is not None:
2619 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2620 append('add_group %s %d' % (g, u.tgt_size))
2621 if (u.src_size is not None and u.tgt_size is not None and
2622 u.src_size < u.tgt_size):
2623 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2624 append('resize_group %s %d' % (g, u.tgt_size))
2625
2626 for p, u in self._partition_updates.items():
2627 if u.tgt_group and not u.src_group:
2628 comment('Add partition %s to group %s' % (p, u.tgt_group))
2629 append('add %s %s' % (p, u.tgt_group))
2630
2631 for p, u in self._partition_updates.items():
2632 if u.tgt_size and u.src_size < u.tgt_size:
2633 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2634 append('resize %s %d' % (p, u.tgt_size))
2635
2636 for p, u in self._partition_updates.items():
2637 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2638 comment('Move partition %s from default to %s' %
2639 (p, u.tgt_group))
2640 append('move %s %s' % (p, u.tgt_group))