blob: 0594b792d7a5a24e89b460fece4da034afc1d592 [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
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800557def AppendAVBSigningArgs(cmd, partition):
558 """Append signing arguments for avbtool."""
559 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
560 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
561 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
562 if key_path and algorithm:
563 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700564 avb_salt = OPTIONS.info_dict.get("avb_salt")
565 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700566 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700567 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800568
569
Tao Bao02a08592018-07-22 12:40:45 -0700570def GetAvbChainedPartitionArg(partition, info_dict, key=None):
571 """Constructs and returns the arg to build or verify a chained partition.
572
573 Args:
574 partition: The partition name.
575 info_dict: The info dict to look up the key info and rollback index
576 location.
577 key: The key to be used for building or verifying the partition. Defaults to
578 the key listed in info_dict.
579
580 Returns:
581 A string of form "partition:rollback_index_location:key" that can be used to
582 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700583 """
584 if key is None:
585 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao1ac886e2019-06-26 11:58:22 -0700586 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700587 rollback_index_location = info_dict[
588 "avb_" + partition + "_rollback_index_location"]
589 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
590
591
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700592def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800593 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700594 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700595
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700596 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800597 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
598 we are building a two-step special image (i.e. building a recovery image to
599 be loaded into /boot in two-step OTAs).
600
601 Return the image data, or None if sourcedir does not appear to contains files
602 for building the requested image.
603 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700604
605 def make_ramdisk():
606 ramdisk_img = tempfile.NamedTemporaryFile()
607
608 if os.access(fs_config_file, os.F_OK):
609 cmd = ["mkbootfs", "-f", fs_config_file,
610 os.path.join(sourcedir, "RAMDISK")]
611 else:
612 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
613 p1 = Run(cmd, stdout=subprocess.PIPE)
614 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
615
616 p2.wait()
617 p1.wait()
618 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
619 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
620
621 return ramdisk_img
622
623 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
624 return None
625
626 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700627 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700628
Doug Zongkerd5131602012-08-02 14:46:42 -0700629 if info_dict is None:
630 info_dict = OPTIONS.info_dict
631
Doug Zongkereef39442009-04-02 12:14:19 -0700632 img = tempfile.NamedTemporaryFile()
633
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700634 if has_ramdisk:
635 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700636
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800637 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
638 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
639
640 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700641
Benoit Fradina45a8682014-07-14 21:00:43 +0200642 fn = os.path.join(sourcedir, "second")
643 if os.access(fn, os.F_OK):
644 cmd.append("--second")
645 cmd.append(fn)
646
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800647 fn = os.path.join(sourcedir, "dtb")
648 if os.access(fn, os.F_OK):
649 cmd.append("--dtb")
650 cmd.append(fn)
651
Doug Zongker171f1cd2009-06-15 22:36:37 -0700652 fn = os.path.join(sourcedir, "cmdline")
653 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700654 cmd.append("--cmdline")
655 cmd.append(open(fn).read().rstrip("\n"))
656
657 fn = os.path.join(sourcedir, "base")
658 if os.access(fn, os.F_OK):
659 cmd.append("--base")
660 cmd.append(open(fn).read().rstrip("\n"))
661
Ying Wang4de6b5b2010-08-25 14:29:34 -0700662 fn = os.path.join(sourcedir, "pagesize")
663 if os.access(fn, os.F_OK):
664 cmd.append("--pagesize")
665 cmd.append(open(fn).read().rstrip("\n"))
666
Tao Bao76def242017-11-21 09:25:31 -0800667 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700668 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700669 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700670
Tao Bao76def242017-11-21 09:25:31 -0800671 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000672 if args and args.strip():
673 cmd.extend(shlex.split(args))
674
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700675 if has_ramdisk:
676 cmd.extend(["--ramdisk", ramdisk_img.name])
677
Tao Baod95e9fd2015-03-29 23:07:41 -0700678 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800679 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700680 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700681 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700682 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700683 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700684
Tao Baobf70c312017-07-11 17:27:55 -0700685 # "boot" or "recovery", without extension.
686 partition_name = os.path.basename(sourcedir).lower()
687
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800688 if partition_name == "recovery":
689 if info_dict.get("include_recovery_dtbo") == "true":
690 fn = os.path.join(sourcedir, "recovery_dtbo")
691 cmd.extend(["--recovery_dtbo", fn])
692 if info_dict.get("include_recovery_acpio") == "true":
693 fn = os.path.join(sourcedir, "recovery_acpio")
694 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700695
Tao Bao986ee862018-10-04 15:46:16 -0700696 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700697
Tao Bao76def242017-11-21 09:25:31 -0800698 if (info_dict.get("boot_signer") == "true" and
699 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800700 # Hard-code the path as "/boot" for two-step special recovery image (which
701 # will be loaded into /boot during the two-step OTA).
702 if two_step_image:
703 path = "/boot"
704 else:
Tao Baobf70c312017-07-11 17:27:55 -0700705 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700706 cmd = [OPTIONS.boot_signer_path]
707 cmd.extend(OPTIONS.boot_signer_args)
708 cmd.extend([path, img.name,
709 info_dict["verity_key"] + ".pk8",
710 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700711 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700712
Tao Baod95e9fd2015-03-29 23:07:41 -0700713 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800714 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700715 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700716 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800717 # We have switched from the prebuilt futility binary to using the tool
718 # (futility-host) built from the source. Override the setting in the old
719 # TF.zip.
720 futility = info_dict["futility"]
721 if futility.startswith("prebuilts/"):
722 futility = "futility-host"
723 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700724 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700725 info_dict["vboot_key"] + ".vbprivk",
726 info_dict["vboot_subkey"] + ".vbprivk",
727 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700728 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700729 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700730
Tao Baof3282b42015-04-01 11:21:55 -0700731 # Clean up the temp files.
732 img_unsigned.close()
733 img_keyblock.close()
734
David Zeuthen8fecb282017-12-01 16:24:01 -0500735 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800736 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700737 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500738 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400739 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700740 "--partition_size", str(part_size), "--partition_name",
741 partition_name]
742 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500743 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400744 if args and args.strip():
745 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700746 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500747
748 img.seek(os.SEEK_SET, 0)
749 data = img.read()
750
751 if has_ramdisk:
752 ramdisk_img.close()
753 img.close()
754
755 return data
756
757
Doug Zongkerd5131602012-08-02 14:46:42 -0700758def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800759 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700760 """Return a File object with the desired bootable image.
761
762 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
763 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
764 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700765
Doug Zongker55d93282011-01-25 17:03:34 -0800766 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
767 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700768 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800769 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700770
771 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
772 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700773 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700774 return File.FromLocalFile(name, prebuilt_path)
775
Tao Bao32fcdab2018-10-12 10:30:39 -0700776 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700777
778 if info_dict is None:
779 info_dict = OPTIONS.info_dict
780
781 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800782 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
783 # for recovery.
784 has_ramdisk = (info_dict.get("system_root_image") != "true" or
785 prebuilt_name != "boot.img" or
786 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700787
Doug Zongker6f1d0312014-08-22 08:07:12 -0700788 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400789 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
790 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800791 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700792 if data:
793 return File(name, data)
794 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800795
Doug Zongkereef39442009-04-02 12:14:19 -0700796
Narayan Kamatha07bf042017-08-14 14:49:21 +0100797def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800798 """Gunzips the given gzip compressed file to a given output file."""
799 with gzip.open(in_filename, "rb") as in_file, \
800 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100801 shutil.copyfileobj(in_file, out_file)
802
803
Tao Bao0ff15de2019-03-20 11:26:06 -0700804def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800805 """Unzips the archive to the given directory.
806
807 Args:
808 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800809 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700810 patterns: Files to unzip from the archive. If omitted, will unzip the entire
811 archvie. Non-matching patterns will be filtered out. If there's no match
812 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800813 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800814 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700815 if patterns is not None:
816 # Filter out non-matching patterns. unzip will complain otherwise.
817 with zipfile.ZipFile(filename) as input_zip:
818 names = input_zip.namelist()
819 filtered = [
820 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
821
822 # There isn't any matching files. Don't unzip anything.
823 if not filtered:
824 return
825 cmd.extend(filtered)
826
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800827 RunAndCheckOutput(cmd)
828
829
Doug Zongker75f17362009-12-08 13:46:44 -0800830def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800831 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800832
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800833 Args:
834 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
835 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
836
837 pattern: Files to unzip from the archive. If omitted, will unzip the entire
838 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800839
Tao Bao1c830bf2017-12-25 10:43:47 -0800840 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800841 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800842 """
Doug Zongkereef39442009-04-02 12:14:19 -0700843
Tao Bao1c830bf2017-12-25 10:43:47 -0800844 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800845 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
846 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800847 UnzipToDir(m.group(1), tmp, pattern)
848 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800849 filename = m.group(1)
850 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800851 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800852
Tao Baodba59ee2018-01-09 13:21:02 -0800853 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700854
855
Yifan Hong8a66a712019-04-04 15:37:57 -0700856def GetUserImage(which, tmpdir, input_zip,
857 info_dict=None,
858 allow_shared_blocks=None,
859 hashtree_info_generator=None,
860 reset_file_map=False):
861 """Returns an Image object suitable for passing to BlockImageDiff.
862
863 This function loads the specified image from the given path. If the specified
864 image is sparse, it also performs additional processing for OTA purpose. For
865 example, it always adds block 0 to clobbered blocks list. It also detects
866 files that cannot be reconstructed from the block list, for whom we should
867 avoid applying imgdiff.
868
869 Args:
870 which: The partition name.
871 tmpdir: The directory that contains the prebuilt image and block map file.
872 input_zip: The target-files ZIP archive.
873 info_dict: The dict to be looked up for relevant info.
874 allow_shared_blocks: If image is sparse, whether having shared blocks is
875 allowed. If none, it is looked up from info_dict.
876 hashtree_info_generator: If present and image is sparse, generates the
877 hashtree_info for this sparse image.
878 reset_file_map: If true and image is sparse, reset file map before returning
879 the image.
880 Returns:
881 A Image object. If it is a sparse image and reset_file_map is False, the
882 image will have file_map info loaded.
883 """
Tao Baoc1a1ec32019-06-18 16:29:37 -0700884 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -0700885 info_dict = LoadInfoDict(input_zip)
886
887 is_sparse = info_dict.get("extfs_sparse_flag")
888
889 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
890 # shared blocks (i.e. some blocks will show up in multiple files' block
891 # list). We can only allocate such shared blocks to the first "owner", and
892 # disable imgdiff for all later occurrences.
893 if allow_shared_blocks is None:
894 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
895
896 if is_sparse:
897 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
898 hashtree_info_generator)
899 if reset_file_map:
900 img.ResetFileMap()
901 return img
902 else:
903 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
904
905
906def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
907 """Returns a Image object suitable for passing to BlockImageDiff.
908
909 This function loads the specified non-sparse image from the given path.
910
911 Args:
912 which: The partition name.
913 tmpdir: The directory that contains the prebuilt image and block map file.
914 Returns:
915 A Image object.
916 """
917 path = os.path.join(tmpdir, "IMAGES", which + ".img")
918 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
919
920 # The image and map files must have been created prior to calling
921 # ota_from_target_files.py (since LMP).
922 assert os.path.exists(path) and os.path.exists(mappath)
923
Tianjie Xu41976c72019-07-03 13:57:01 -0700924 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
925
Yifan Hong8a66a712019-04-04 15:37:57 -0700926
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700927def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
928 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800929 """Returns a SparseImage object suitable for passing to BlockImageDiff.
930
931 This function loads the specified sparse image from the given path, and
932 performs additional processing for OTA purpose. For example, it always adds
933 block 0 to clobbered blocks list. It also detects files that cannot be
934 reconstructed from the block list, for whom we should avoid applying imgdiff.
935
936 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700937 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800938 tmpdir: The directory that contains the prebuilt image and block map file.
939 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800940 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700941 hashtree_info_generator: If present, generates the hashtree_info for this
942 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800943 Returns:
944 A SparseImage object, with file_map info loaded.
945 """
Tao Baoc765cca2018-01-31 17:32:40 -0800946 path = os.path.join(tmpdir, "IMAGES", which + ".img")
947 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
948
949 # The image and map files must have been created prior to calling
950 # ota_from_target_files.py (since LMP).
951 assert os.path.exists(path) and os.path.exists(mappath)
952
953 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
954 # it to clobbered_blocks so that it will be written to the target
955 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
956 clobbered_blocks = "0"
957
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700958 image = sparse_img.SparseImage(
959 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
960 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800961
962 # block.map may contain less blocks, because mke2fs may skip allocating blocks
963 # if they contain all zeros. We can't reconstruct such a file from its block
964 # list. Tag such entries accordingly. (Bug: 65213616)
965 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800966 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700967 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800968 continue
969
Tom Cherryd14b8952018-08-09 14:26:00 -0700970 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
971 # filename listed in system.map may contain an additional leading slash
972 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
973 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -0800974 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -0700975
Tom Cherryd14b8952018-08-09 14:26:00 -0700976 # Special handling another case, where files not under /system
977 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700978 if which == 'system' and not arcname.startswith('SYSTEM'):
979 arcname = 'ROOT/' + arcname
980
981 assert arcname in input_zip.namelist(), \
982 "Failed to find the ZIP entry for {}".format(entry)
983
Tao Baoc765cca2018-01-31 17:32:40 -0800984 info = input_zip.getinfo(arcname)
985 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800986
987 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800988 # image, check the original block list to determine its completeness. Note
989 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800990 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800991 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800992
Tao Baoc765cca2018-01-31 17:32:40 -0800993 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
994 ranges.extra['incomplete'] = True
995
996 return image
997
998
Doug Zongkereef39442009-04-02 12:14:19 -0700999def GetKeyPasswords(keylist):
1000 """Given a list of keys, prompt the user to enter passwords for
1001 those which require them. Return a {key: password} dict. password
1002 will be None if the key has no password."""
1003
Doug Zongker8ce7c252009-05-22 13:34:54 -07001004 no_passwords = []
1005 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001006 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001007 devnull = open("/dev/null", "w+b")
1008 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001009 # We don't need a password for things that aren't really keys.
1010 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001011 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001012 continue
1013
T.R. Fullhart37e10522013-03-18 10:31:26 -07001014 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001015 "-inform", "DER", "-nocrypt"],
1016 stdin=devnull.fileno(),
1017 stdout=devnull.fileno(),
1018 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001019 p.communicate()
1020 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001021 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001022 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001023 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001024 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1025 "-inform", "DER", "-passin", "pass:"],
1026 stdin=devnull.fileno(),
1027 stdout=devnull.fileno(),
1028 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001029 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001030 if p.returncode == 0:
1031 # Encrypted key with empty string as password.
1032 key_passwords[k] = ''
1033 elif stderr.startswith('Error decrypting key'):
1034 # Definitely encrypted key.
1035 # It would have said "Error reading key" if it didn't parse correctly.
1036 need_passwords.append(k)
1037 else:
1038 # Potentially, a type of key that openssl doesn't understand.
1039 # We'll let the routines in signapk.jar handle it.
1040 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001041 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001042
T.R. Fullhart37e10522013-03-18 10:31:26 -07001043 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001044 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001045 return key_passwords
1046
1047
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001048def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001049 """Gets the minSdkVersion declared in the APK.
1050
changho.shin0f125362019-07-08 10:59:00 +09001051 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001052 This can be both a decimal number (API Level) or a codename.
1053
1054 Args:
1055 apk_name: The APK filename.
1056
1057 Returns:
1058 The parsed SDK version string.
1059
1060 Raises:
1061 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001062 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001063 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001064 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001065 stderr=subprocess.PIPE)
1066 stdoutdata, stderrdata = proc.communicate()
1067 if proc.returncode != 0:
1068 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001069 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001070 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001071
Tao Baof47bf0f2018-03-21 23:28:51 -07001072 for line in stdoutdata.split("\n"):
1073 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001074 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1075 if m:
1076 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001077 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001078
1079
1080def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001081 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001082
Tao Baof47bf0f2018-03-21 23:28:51 -07001083 If minSdkVersion is set to a codename, it is translated to a number using the
1084 provided map.
1085
1086 Args:
1087 apk_name: The APK filename.
1088
1089 Returns:
1090 The parsed SDK version number.
1091
1092 Raises:
1093 ExternalError: On failing to get the min SDK version number.
1094 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001095 version = GetMinSdkVersion(apk_name)
1096 try:
1097 return int(version)
1098 except ValueError:
1099 # Not a decimal number. Codename?
1100 if version in codename_to_api_level_map:
1101 return codename_to_api_level_map[version]
1102 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001103 raise ExternalError(
1104 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1105 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001106
1107
1108def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001109 codename_to_api_level_map=None, whole_file=False,
1110 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001111 """Sign the input_name zip/jar/apk, producing output_name. Use the
1112 given key and password (the latter may be None if the key does not
1113 have a password.
1114
Doug Zongker951495f2009-08-14 12:44:19 -07001115 If whole_file is true, use the "-w" option to SignApk to embed a
1116 signature that covers the whole file in the archive comment of the
1117 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001118
1119 min_api_level is the API Level (int) of the oldest platform this file may end
1120 up on. If not specified for an APK, the API Level is obtained by interpreting
1121 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1122
1123 codename_to_api_level_map is needed to translate the codename which may be
1124 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001125
1126 Caller may optionally specify extra args to be passed to SignApk, which
1127 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001128 """
Tao Bao76def242017-11-21 09:25:31 -08001129 if codename_to_api_level_map is None:
1130 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001131 if extra_signapk_args is None:
1132 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001133
Alex Klyubin9667b182015-12-10 13:38:50 -08001134 java_library_path = os.path.join(
1135 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1136
Tao Baoe95540e2016-11-08 12:08:53 -08001137 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1138 ["-Djava.library.path=" + java_library_path,
1139 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001140 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001141 if whole_file:
1142 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001143
1144 min_sdk_version = min_api_level
1145 if min_sdk_version is None:
1146 if not whole_file:
1147 min_sdk_version = GetMinSdkVersionInt(
1148 input_name, codename_to_api_level_map)
1149 if min_sdk_version is not None:
1150 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1151
T.R. Fullhart37e10522013-03-18 10:31:26 -07001152 cmd.extend([key + OPTIONS.public_key_suffix,
1153 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001154 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001155
Tao Bao73dd4f42018-10-04 16:25:33 -07001156 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001157 if password is not None:
1158 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001159 stdoutdata, _ = proc.communicate(password)
1160 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001161 raise ExternalError(
1162 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001163 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001164
Doug Zongkereef39442009-04-02 12:14:19 -07001165
Doug Zongker37974732010-09-16 17:44:38 -07001166def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001167 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001168
Tao Bao9dd909e2017-11-14 11:27:32 -08001169 For non-AVB images, raise exception if the data is too big. Print a warning
1170 if the data is nearing the maximum size.
1171
1172 For AVB images, the actual image size should be identical to the limit.
1173
1174 Args:
1175 data: A string that contains all the data for the partition.
1176 target: The partition name. The ".img" suffix is optional.
1177 info_dict: The dict to be looked up for relevant info.
1178 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001179 if target.endswith(".img"):
1180 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001181 mount_point = "/" + target
1182
Ying Wangf8824af2014-06-03 14:07:27 -07001183 fs_type = None
1184 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001185 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001186 if mount_point == "/userdata":
1187 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001188 p = info_dict["fstab"][mount_point]
1189 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001190 device = p.device
1191 if "/" in device:
1192 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001193 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001194 if not fs_type or not limit:
1195 return
Doug Zongkereef39442009-04-02 12:14:19 -07001196
Andrew Boie0f9aec82012-02-14 09:32:52 -08001197 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001198 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1199 # path.
1200 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1201 if size != limit:
1202 raise ExternalError(
1203 "Mismatching image size for %s: expected %d actual %d" % (
1204 target, limit, size))
1205 else:
1206 pct = float(size) * 100.0 / limit
1207 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1208 if pct >= 99.0:
1209 raise ExternalError(msg)
1210 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001211 logger.warning("\n WARNING: %s\n", msg)
1212 else:
1213 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001214
1215
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001216def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001217 """Parses the APK certs info from a given target-files zip.
1218
1219 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1220 tuple with the following elements: (1) a dictionary that maps packages to
1221 certs (based on the "certificate" and "private_key" attributes in the file;
1222 (2) a string representing the extension of compressed APKs in the target files
1223 (e.g ".gz", ".bro").
1224
1225 Args:
1226 tf_zip: The input target_files ZipFile (already open).
1227
1228 Returns:
1229 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1230 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1231 no compressed APKs.
1232 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001233 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001234 compressed_extension = None
1235
Tao Bao0f990332017-09-08 19:02:54 -07001236 # META/apkcerts.txt contains the info for _all_ the packages known at build
1237 # time. Filter out the ones that are not installed.
1238 installed_files = set()
1239 for name in tf_zip.namelist():
1240 basename = os.path.basename(name)
1241 if basename:
1242 installed_files.add(basename)
1243
Tao Baoda30cfa2017-12-01 16:19:46 -08001244 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001245 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001246 if not line:
1247 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001248 m = re.match(
1249 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1250 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1251 line)
1252 if not m:
1253 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001254
Tao Bao818ddf52018-01-05 11:17:34 -08001255 matches = m.groupdict()
1256 cert = matches["CERT"]
1257 privkey = matches["PRIVKEY"]
1258 name = matches["NAME"]
1259 this_compressed_extension = matches["COMPRESSED"]
1260
1261 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1262 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1263 if cert in SPECIAL_CERT_STRINGS and not privkey:
1264 certmap[name] = cert
1265 elif (cert.endswith(OPTIONS.public_key_suffix) and
1266 privkey.endswith(OPTIONS.private_key_suffix) and
1267 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1268 certmap[name] = cert[:-public_key_suffix_len]
1269 else:
1270 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1271
1272 if not this_compressed_extension:
1273 continue
1274
1275 # Only count the installed files.
1276 filename = name + '.' + this_compressed_extension
1277 if filename not in installed_files:
1278 continue
1279
1280 # Make sure that all the values in the compression map have the same
1281 # extension. We don't support multiple compression methods in the same
1282 # system image.
1283 if compressed_extension:
1284 if this_compressed_extension != compressed_extension:
1285 raise ValueError(
1286 "Multiple compressed extensions: {} vs {}".format(
1287 compressed_extension, this_compressed_extension))
1288 else:
1289 compressed_extension = this_compressed_extension
1290
1291 return (certmap,
1292 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001293
1294
Doug Zongkereef39442009-04-02 12:14:19 -07001295COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001296Global options
1297
1298 -p (--path) <dir>
1299 Prepend <dir>/bin to the list of places to search for binaries run by this
1300 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001301
Doug Zongker05d3dea2009-06-22 11:32:31 -07001302 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001303 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001304
Tao Bao30df8b42018-04-23 15:32:53 -07001305 -x (--extra) <key=value>
1306 Add a key/value pair to the 'extras' dict, which device-specific extension
1307 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001308
Doug Zongkereef39442009-04-02 12:14:19 -07001309 -v (--verbose)
1310 Show command lines being executed.
1311
1312 -h (--help)
1313 Display this usage message and exit.
1314"""
1315
1316def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001317 print(docstring.rstrip("\n"))
1318 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001319
1320
1321def ParseOptions(argv,
1322 docstring,
1323 extra_opts="", extra_long_opts=(),
1324 extra_option_handler=None):
1325 """Parse the options in argv and return any arguments that aren't
1326 flags. docstring is the calling module's docstring, to be displayed
1327 for errors and -h. extra_opts and extra_long_opts are for flags
1328 defined by the caller, which are processed by passing them to
1329 extra_option_handler."""
1330
1331 try:
1332 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001333 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001334 ["help", "verbose", "path=", "signapk_path=",
1335 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001336 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001337 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1338 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001339 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001340 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001341 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001342 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001343 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001344 sys.exit(2)
1345
Doug Zongkereef39442009-04-02 12:14:19 -07001346 for o, a in opts:
1347 if o in ("-h", "--help"):
1348 Usage(docstring)
1349 sys.exit()
1350 elif o in ("-v", "--verbose"):
1351 OPTIONS.verbose = True
1352 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001353 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001354 elif o in ("--signapk_path",):
1355 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001356 elif o in ("--signapk_shared_library_path",):
1357 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001358 elif o in ("--extra_signapk_args",):
1359 OPTIONS.extra_signapk_args = shlex.split(a)
1360 elif o in ("--java_path",):
1361 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001362 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001363 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001364 elif o in ("--public_key_suffix",):
1365 OPTIONS.public_key_suffix = a
1366 elif o in ("--private_key_suffix",):
1367 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001368 elif o in ("--boot_signer_path",):
1369 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001370 elif o in ("--boot_signer_args",):
1371 OPTIONS.boot_signer_args = shlex.split(a)
1372 elif o in ("--verity_signer_path",):
1373 OPTIONS.verity_signer_path = a
1374 elif o in ("--verity_signer_args",):
1375 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001376 elif o in ("-s", "--device_specific"):
1377 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001378 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001379 key, value = a.split("=", 1)
1380 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001381 else:
1382 if extra_option_handler is None or not extra_option_handler(o, a):
1383 assert False, "unknown option \"%s\"" % (o,)
1384
Doug Zongker85448772014-09-09 14:59:20 -07001385 if OPTIONS.search_path:
1386 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1387 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001388
1389 return args
1390
1391
Tao Bao4c851b12016-09-19 13:54:38 -07001392def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001393 """Make a temp file and add it to the list of things to be deleted
1394 when Cleanup() is called. Return the filename."""
1395 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1396 os.close(fd)
1397 OPTIONS.tempfiles.append(fn)
1398 return fn
1399
1400
Tao Bao1c830bf2017-12-25 10:43:47 -08001401def MakeTempDir(prefix='tmp', suffix=''):
1402 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1403
1404 Returns:
1405 The absolute pathname of the new directory.
1406 """
1407 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1408 OPTIONS.tempfiles.append(dir_name)
1409 return dir_name
1410
1411
Doug Zongkereef39442009-04-02 12:14:19 -07001412def Cleanup():
1413 for i in OPTIONS.tempfiles:
1414 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001415 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001416 else:
1417 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001418 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001419
1420
1421class PasswordManager(object):
1422 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001423 self.editor = os.getenv("EDITOR")
1424 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001425
1426 def GetPasswords(self, items):
1427 """Get passwords corresponding to each string in 'items',
1428 returning a dict. (The dict may have keys in addition to the
1429 values in 'items'.)
1430
1431 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1432 user edit that file to add more needed passwords. If no editor is
1433 available, or $ANDROID_PW_FILE isn't define, prompts the user
1434 interactively in the ordinary way.
1435 """
1436
1437 current = self.ReadFile()
1438
1439 first = True
1440 while True:
1441 missing = []
1442 for i in items:
1443 if i not in current or not current[i]:
1444 missing.append(i)
1445 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001446 if not missing:
1447 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001448
1449 for i in missing:
1450 current[i] = ""
1451
1452 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001453 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001454 if sys.version_info[0] >= 3:
1455 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001456 answer = raw_input("try to edit again? [y]> ").strip()
1457 if answer and answer[0] not in 'yY':
1458 raise RuntimeError("key passwords unavailable")
1459 first = False
1460
1461 current = self.UpdateAndReadFile(current)
1462
Dan Albert8b72aef2015-03-23 19:13:21 -07001463 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001464 """Prompt the user to enter a value (password) for each key in
1465 'current' whose value is fales. Returns a new dict with all the
1466 values.
1467 """
1468 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001469 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001470 if v:
1471 result[k] = v
1472 else:
1473 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001474 result[k] = getpass.getpass(
1475 "Enter password for %s key> " % k).strip()
1476 if result[k]:
1477 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001478 return result
1479
1480 def UpdateAndReadFile(self, current):
1481 if not self.editor or not self.pwfile:
1482 return self.PromptResult(current)
1483
1484 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001485 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001486 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1487 f.write("# (Additional spaces are harmless.)\n\n")
1488
1489 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001490 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001491 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001492 f.write("[[[ %s ]]] %s\n" % (v, k))
1493 if not v and first_line is None:
1494 # position cursor on first line with no password.
1495 first_line = i + 4
1496 f.close()
1497
Tao Bao986ee862018-10-04 15:46:16 -07001498 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001499
1500 return self.ReadFile()
1501
1502 def ReadFile(self):
1503 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001504 if self.pwfile is None:
1505 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001506 try:
1507 f = open(self.pwfile, "r")
1508 for line in f:
1509 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001510 if not line or line[0] == '#':
1511 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001512 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1513 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001514 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001515 else:
1516 result[m.group(2)] = m.group(1)
1517 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001518 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001519 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001520 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001521 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001522
1523
Dan Albert8e0178d2015-01-27 15:53:15 -08001524def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1525 compress_type=None):
1526 import datetime
1527
1528 # http://b/18015246
1529 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1530 # for files larger than 2GiB. We can work around this by adjusting their
1531 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1532 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1533 # it isn't clear to me exactly what circumstances cause this).
1534 # `zipfile.write()` must be used directly to work around this.
1535 #
1536 # This mess can be avoided if we port to python3.
1537 saved_zip64_limit = zipfile.ZIP64_LIMIT
1538 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1539
1540 if compress_type is None:
1541 compress_type = zip_file.compression
1542 if arcname is None:
1543 arcname = filename
1544
1545 saved_stat = os.stat(filename)
1546
1547 try:
1548 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1549 # file to be zipped and reset it when we're done.
1550 os.chmod(filename, perms)
1551
1552 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001553 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1554 # intentional. zip stores datetimes in local time without a time zone
1555 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1556 # in the zip archive.
1557 local_epoch = datetime.datetime.fromtimestamp(0)
1558 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001559 os.utime(filename, (timestamp, timestamp))
1560
1561 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1562 finally:
1563 os.chmod(filename, saved_stat.st_mode)
1564 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1565 zipfile.ZIP64_LIMIT = saved_zip64_limit
1566
1567
Tao Bao58c1b962015-05-20 09:32:18 -07001568def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001569 compress_type=None):
1570 """Wrap zipfile.writestr() function to work around the zip64 limit.
1571
1572 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1573 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1574 when calling crc32(bytes).
1575
1576 But it still works fine to write a shorter string into a large zip file.
1577 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1578 when we know the string won't be too long.
1579 """
1580
1581 saved_zip64_limit = zipfile.ZIP64_LIMIT
1582 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1583
1584 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1585 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001586 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001587 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001588 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001589 else:
Tao Baof3282b42015-04-01 11:21:55 -07001590 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001591 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1592 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1593 # such a case (since
1594 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1595 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1596 # permission bits. We follow the logic in Python 3 to get consistent
1597 # behavior between using the two versions.
1598 if not zinfo.external_attr:
1599 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001600
1601 # If compress_type is given, it overrides the value in zinfo.
1602 if compress_type is not None:
1603 zinfo.compress_type = compress_type
1604
Tao Bao58c1b962015-05-20 09:32:18 -07001605 # If perms is given, it has a priority.
1606 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001607 # If perms doesn't set the file type, mark it as a regular file.
1608 if perms & 0o770000 == 0:
1609 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001610 zinfo.external_attr = perms << 16
1611
Tao Baof3282b42015-04-01 11:21:55 -07001612 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001613 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1614
Dan Albert8b72aef2015-03-23 19:13:21 -07001615 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001616 zipfile.ZIP64_LIMIT = saved_zip64_limit
1617
1618
Tao Bao89d7ab22017-12-14 17:05:33 -08001619def ZipDelete(zip_filename, entries):
1620 """Deletes entries from a ZIP file.
1621
1622 Since deleting entries from a ZIP file is not supported, it shells out to
1623 'zip -d'.
1624
1625 Args:
1626 zip_filename: The name of the ZIP file.
1627 entries: The name of the entry, or the list of names to be deleted.
1628
1629 Raises:
1630 AssertionError: In case of non-zero return from 'zip'.
1631 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001632 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001633 entries = [entries]
1634 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001635 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001636
1637
Tao Baof3282b42015-04-01 11:21:55 -07001638def ZipClose(zip_file):
1639 # http://b/18015246
1640 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1641 # central directory.
1642 saved_zip64_limit = zipfile.ZIP64_LIMIT
1643 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1644
1645 zip_file.close()
1646
1647 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001648
1649
1650class DeviceSpecificParams(object):
1651 module = None
1652 def __init__(self, **kwargs):
1653 """Keyword arguments to the constructor become attributes of this
1654 object, which is passed to all functions in the device-specific
1655 module."""
Tao Bao38884282019-07-10 22:20:56 -07001656 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07001657 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001658 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001659
1660 if self.module is None:
1661 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001662 if not path:
1663 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001664 try:
1665 if os.path.isdir(path):
1666 info = imp.find_module("releasetools", [path])
1667 else:
1668 d, f = os.path.split(path)
1669 b, x = os.path.splitext(f)
1670 if x == ".py":
1671 f = b
1672 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001673 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001674 self.module = imp.load_module("device_specific", *info)
1675 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001676 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001677
1678 def _DoCall(self, function_name, *args, **kwargs):
1679 """Call the named function in the device-specific module, passing
1680 the given args and kwargs. The first argument to the call will be
1681 the DeviceSpecific object itself. If there is no module, or the
1682 module does not define the function, return the value of the
1683 'default' kwarg (which itself defaults to None)."""
1684 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001685 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001686 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1687
1688 def FullOTA_Assertions(self):
1689 """Called after emitting the block of assertions at the top of a
1690 full OTA package. Implementations can add whatever additional
1691 assertions they like."""
1692 return self._DoCall("FullOTA_Assertions")
1693
Doug Zongkere5ff5902012-01-17 10:55:37 -08001694 def FullOTA_InstallBegin(self):
1695 """Called at the start of full OTA installation."""
1696 return self._DoCall("FullOTA_InstallBegin")
1697
Yifan Hong10c530d2018-12-27 17:34:18 -08001698 def FullOTA_GetBlockDifferences(self):
1699 """Called during full OTA installation and verification.
1700 Implementation should return a list of BlockDifference objects describing
1701 the update on each additional partitions.
1702 """
1703 return self._DoCall("FullOTA_GetBlockDifferences")
1704
Doug Zongker05d3dea2009-06-22 11:32:31 -07001705 def FullOTA_InstallEnd(self):
1706 """Called at the end of full OTA installation; typically this is
1707 used to install the image for the device's baseband processor."""
1708 return self._DoCall("FullOTA_InstallEnd")
1709
1710 def IncrementalOTA_Assertions(self):
1711 """Called after emitting the block of assertions at the top of an
1712 incremental OTA package. Implementations can add whatever
1713 additional assertions they like."""
1714 return self._DoCall("IncrementalOTA_Assertions")
1715
Doug Zongkere5ff5902012-01-17 10:55:37 -08001716 def IncrementalOTA_VerifyBegin(self):
1717 """Called at the start of the verification phase of incremental
1718 OTA installation; additional checks can be placed here to abort
1719 the script before any changes are made."""
1720 return self._DoCall("IncrementalOTA_VerifyBegin")
1721
Doug Zongker05d3dea2009-06-22 11:32:31 -07001722 def IncrementalOTA_VerifyEnd(self):
1723 """Called at the end of the verification phase of incremental OTA
1724 installation; additional checks can be placed here to abort the
1725 script before any changes are made."""
1726 return self._DoCall("IncrementalOTA_VerifyEnd")
1727
Doug Zongkere5ff5902012-01-17 10:55:37 -08001728 def IncrementalOTA_InstallBegin(self):
1729 """Called at the start of incremental OTA installation (after
1730 verification is complete)."""
1731 return self._DoCall("IncrementalOTA_InstallBegin")
1732
Yifan Hong10c530d2018-12-27 17:34:18 -08001733 def IncrementalOTA_GetBlockDifferences(self):
1734 """Called during incremental OTA installation and verification.
1735 Implementation should return a list of BlockDifference objects describing
1736 the update on each additional partitions.
1737 """
1738 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1739
Doug Zongker05d3dea2009-06-22 11:32:31 -07001740 def IncrementalOTA_InstallEnd(self):
1741 """Called at the end of incremental OTA installation; typically
1742 this is used to install the image for the device's baseband
1743 processor."""
1744 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001745
Tao Bao9bc6bb22015-11-09 16:58:28 -08001746 def VerifyOTA_Assertions(self):
1747 return self._DoCall("VerifyOTA_Assertions")
1748
Tao Bao76def242017-11-21 09:25:31 -08001749
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001750class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001751 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001752 self.name = name
1753 self.data = data
1754 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001755 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001756 self.sha1 = sha1(data).hexdigest()
1757
1758 @classmethod
1759 def FromLocalFile(cls, name, diskname):
1760 f = open(diskname, "rb")
1761 data = f.read()
1762 f.close()
1763 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001764
1765 def WriteToTemp(self):
1766 t = tempfile.NamedTemporaryFile()
1767 t.write(self.data)
1768 t.flush()
1769 return t
1770
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001771 def WriteToDir(self, d):
1772 with open(os.path.join(d, self.name), "wb") as fp:
1773 fp.write(self.data)
1774
Geremy Condra36bd3652014-02-06 19:45:10 -08001775 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001776 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001777
Tao Bao76def242017-11-21 09:25:31 -08001778
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001779DIFF_PROGRAM_BY_EXT = {
1780 ".gz" : "imgdiff",
1781 ".zip" : ["imgdiff", "-z"],
1782 ".jar" : ["imgdiff", "-z"],
1783 ".apk" : ["imgdiff", "-z"],
1784 ".img" : "imgdiff",
1785 }
1786
Tao Bao76def242017-11-21 09:25:31 -08001787
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001788class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001789 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001790 self.tf = tf
1791 self.sf = sf
1792 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001793 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001794
1795 def ComputePatch(self):
1796 """Compute the patch (as a string of data) needed to turn sf into
1797 tf. Returns the same tuple as GetPatch()."""
1798
1799 tf = self.tf
1800 sf = self.sf
1801
Doug Zongker24cd2802012-08-14 16:36:15 -07001802 if self.diff_program:
1803 diff_program = self.diff_program
1804 else:
1805 ext = os.path.splitext(tf.name)[1]
1806 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001807
1808 ttemp = tf.WriteToTemp()
1809 stemp = sf.WriteToTemp()
1810
1811 ext = os.path.splitext(tf.name)[1]
1812
1813 try:
1814 ptemp = tempfile.NamedTemporaryFile()
1815 if isinstance(diff_program, list):
1816 cmd = copy.copy(diff_program)
1817 else:
1818 cmd = [diff_program]
1819 cmd.append(stemp.name)
1820 cmd.append(ttemp.name)
1821 cmd.append(ptemp.name)
1822 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001823 err = []
1824 def run():
1825 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001826 if e:
1827 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001828 th = threading.Thread(target=run)
1829 th.start()
1830 th.join(timeout=300) # 5 mins
1831 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001832 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001833 p.terminate()
1834 th.join(5)
1835 if th.is_alive():
1836 p.kill()
1837 th.join()
1838
Tianjie Xua2a9f992018-01-05 15:15:54 -08001839 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001840 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001841 self.patch = None
1842 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001843 diff = ptemp.read()
1844 finally:
1845 ptemp.close()
1846 stemp.close()
1847 ttemp.close()
1848
1849 self.patch = diff
1850 return self.tf, self.sf, self.patch
1851
1852
1853 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001854 """Returns a tuple of (target_file, source_file, patch_data).
1855
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001856 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001857 computing the patch failed.
1858 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001859 return self.tf, self.sf, self.patch
1860
1861
1862def ComputeDifferences(diffs):
1863 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001864 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001865
1866 # Do the largest files first, to try and reduce the long-pole effect.
1867 by_size = [(i.tf.size, i) for i in diffs]
1868 by_size.sort(reverse=True)
1869 by_size = [i[1] for i in by_size]
1870
1871 lock = threading.Lock()
1872 diff_iter = iter(by_size) # accessed under lock
1873
1874 def worker():
1875 try:
1876 lock.acquire()
1877 for d in diff_iter:
1878 lock.release()
1879 start = time.time()
1880 d.ComputePatch()
1881 dur = time.time() - start
1882 lock.acquire()
1883
1884 tf, sf, patch = d.GetPatch()
1885 if sf.name == tf.name:
1886 name = tf.name
1887 else:
1888 name = "%s (%s)" % (tf.name, sf.name)
1889 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001890 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001891 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001892 logger.info(
1893 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1894 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001895 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001896 except Exception:
1897 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001898 raise
1899
1900 # start worker threads; wait for them all to finish.
1901 threads = [threading.Thread(target=worker)
1902 for i in range(OPTIONS.worker_threads)]
1903 for th in threads:
1904 th.start()
1905 while threads:
1906 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001907
1908
Dan Albert8b72aef2015-03-23 19:13:21 -07001909class BlockDifference(object):
1910 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001911 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001912 self.tgt = tgt
1913 self.src = src
1914 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001915 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001916 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001917
Tao Baodd2a5892015-03-12 12:32:37 -07001918 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001919 version = max(
1920 int(i) for i in
1921 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001922 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001923 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001924
Tianjie Xu41976c72019-07-03 13:57:01 -07001925 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
1926 version=self.version,
1927 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001928 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001929 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001930 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001931 self.touched_src_ranges = b.touched_src_ranges
1932 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001933
Yifan Hong10c530d2018-12-27 17:34:18 -08001934 # On devices with dynamic partitions, for new partitions,
1935 # src is None but OPTIONS.source_info_dict is not.
1936 if OPTIONS.source_info_dict is None:
1937 is_dynamic_build = OPTIONS.info_dict.get(
1938 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001939 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001940 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001941 is_dynamic_build = OPTIONS.source_info_dict.get(
1942 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001943 is_dynamic_source = partition in shlex.split(
1944 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001945
Yifan Hongbb2658d2019-01-25 12:30:58 -08001946 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001947 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1948
Yifan Hongbb2658d2019-01-25 12:30:58 -08001949 # For dynamic partitions builds, check partition list in both source
1950 # and target build because new partitions may be added, and existing
1951 # partitions may be removed.
1952 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1953
Yifan Hong10c530d2018-12-27 17:34:18 -08001954 if is_dynamic:
1955 self.device = 'map_partition("%s")' % partition
1956 else:
1957 if OPTIONS.source_info_dict is None:
1958 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1959 else:
1960 _, device_path = GetTypeAndDevice("/" + partition,
1961 OPTIONS.source_info_dict)
1962 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001963
Tao Baod8d14be2016-02-04 14:26:02 -08001964 @property
1965 def required_cache(self):
1966 return self._required_cache
1967
Tao Bao76def242017-11-21 09:25:31 -08001968 def WriteScript(self, script, output_zip, progress=None,
1969 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001970 if not self.src:
1971 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001972 script.Print("Patching %s image unconditionally..." % (self.partition,))
1973 else:
1974 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001975
Dan Albert8b72aef2015-03-23 19:13:21 -07001976 if progress:
1977 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001978 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001979
1980 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001981 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001982
Tao Bao9bc6bb22015-11-09 16:58:28 -08001983 def WriteStrictVerifyScript(self, script):
1984 """Verify all the blocks in the care_map, including clobbered blocks.
1985
1986 This differs from the WriteVerifyScript() function: a) it prints different
1987 error messages; b) it doesn't allow half-way updated images to pass the
1988 verification."""
1989
1990 partition = self.partition
1991 script.Print("Verifying %s..." % (partition,))
1992 ranges = self.tgt.care_map
1993 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001994 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001995 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1996 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001997 self.device, ranges_str,
1998 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001999 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002000 script.AppendExtra("")
2001
Tao Baod522bdc2016-04-12 15:53:16 -07002002 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002003 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002004
2005 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002006 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002007 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002008
2009 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002010 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002011 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002012 ranges = self.touched_src_ranges
2013 expected_sha1 = self.touched_src_sha1
2014 else:
2015 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2016 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002017
2018 # No blocks to be checked, skipping.
2019 if not ranges:
2020 return
2021
Tao Bao5ece99d2015-05-12 11:42:31 -07002022 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002023 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002024 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002025 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2026 '"%s.patch.dat")) then' % (
2027 self.device, ranges_str, expected_sha1,
2028 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002029 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002030 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002031
Tianjie Xufc3422a2015-12-15 11:53:59 -08002032 if self.version >= 4:
2033
2034 # Bug: 21124327
2035 # When generating incrementals for the system and vendor partitions in
2036 # version 4 or newer, explicitly check the first block (which contains
2037 # the superblock) of the partition to see if it's what we expect. If
2038 # this check fails, give an explicit log message about the partition
2039 # having been remounted R/W (the most likely explanation).
2040 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002041 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002042
2043 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002044 if partition == "system":
2045 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2046 else:
2047 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002048 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002049 'ifelse (block_image_recover({device}, "{ranges}") && '
2050 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002051 'package_extract_file("{partition}.transfer.list"), '
2052 '"{partition}.new.dat", "{partition}.patch.dat"), '
2053 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002054 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002055 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002056 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002057
Tao Baodd2a5892015-03-12 12:32:37 -07002058 # Abort the OTA update. Note that the incremental OTA cannot be applied
2059 # even if it may match the checksum of the target partition.
2060 # a) If version < 3, operations like move and erase will make changes
2061 # unconditionally and damage the partition.
2062 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002063 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002064 if partition == "system":
2065 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2066 else:
2067 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2068 script.AppendExtra((
2069 'abort("E%d: %s partition has unexpected contents");\n'
2070 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002071
Yifan Hong10c530d2018-12-27 17:34:18 -08002072 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002073 partition = self.partition
2074 script.Print('Verifying the updated %s image...' % (partition,))
2075 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2076 ranges = self.tgt.care_map
2077 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002078 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002079 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002080 self.device, ranges_str,
2081 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002082
2083 # Bug: 20881595
2084 # Verify that extended blocks are really zeroed out.
2085 if self.tgt.extended:
2086 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002087 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002088 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002089 self.device, ranges_str,
2090 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002091 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002092 if partition == "system":
2093 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2094 else:
2095 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002096 script.AppendExtra(
2097 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002098 ' abort("E%d: %s partition has unexpected non-zero contents after '
2099 'OTA update");\n'
2100 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002101 else:
2102 script.Print('Verified the updated %s image.' % (partition,))
2103
Tianjie Xu209db462016-05-24 17:34:52 -07002104 if partition == "system":
2105 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2106 else:
2107 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2108
Tao Bao5fcaaef2015-06-01 13:40:49 -07002109 script.AppendExtra(
2110 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002111 ' abort("E%d: %s partition has unexpected contents after OTA '
2112 'update");\n'
2113 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002114
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002115 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002116 ZipWrite(output_zip,
2117 '{}.transfer.list'.format(self.path),
2118 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002119
Tao Bao76def242017-11-21 09:25:31 -08002120 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2121 # its size. Quailty 9 almost triples the compression time but doesn't
2122 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002123 # zip | brotli(quality 6) | brotli(quality 9)
2124 # compressed_size: 942M | 869M (~8% reduced) | 854M
2125 # compression_time: 75s | 265s | 719s
2126 # decompression_time: 15s | 25s | 25s
2127
2128 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002129 brotli_cmd = ['brotli', '--quality=6',
2130 '--output={}.new.dat.br'.format(self.path),
2131 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002132 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002133 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002134
2135 new_data_name = '{}.new.dat.br'.format(self.partition)
2136 ZipWrite(output_zip,
2137 '{}.new.dat.br'.format(self.path),
2138 new_data_name,
2139 compress_type=zipfile.ZIP_STORED)
2140 else:
2141 new_data_name = '{}.new.dat'.format(self.partition)
2142 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2143
Dan Albert8e0178d2015-01-27 15:53:15 -08002144 ZipWrite(output_zip,
2145 '{}.patch.dat'.format(self.path),
2146 '{}.patch.dat'.format(self.partition),
2147 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002148
Tianjie Xu209db462016-05-24 17:34:52 -07002149 if self.partition == "system":
2150 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2151 else:
2152 code = ErrorCode.VENDOR_UPDATE_FAILURE
2153
Yifan Hong10c530d2018-12-27 17:34:18 -08002154 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002155 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002156 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002157 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002158 device=self.device, partition=self.partition,
2159 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002160 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002161
Dan Albert8b72aef2015-03-23 19:13:21 -07002162 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002163 data = source.ReadRangeSet(ranges)
2164 ctx = sha1()
2165
2166 for p in data:
2167 ctx.update(p)
2168
2169 return ctx.hexdigest()
2170
Tao Baoe9b61912015-07-09 17:37:49 -07002171 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2172 """Return the hash value for all zero blocks."""
2173 zero_block = '\x00' * 4096
2174 ctx = sha1()
2175 for _ in range(num_blocks):
2176 ctx.update(zero_block)
2177
2178 return ctx.hexdigest()
2179
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002180
Tianjie Xu41976c72019-07-03 13:57:01 -07002181# Expose these two classes to support vendor-specific scripts
2182DataImage = images.DataImage
2183EmptyImage = images.EmptyImage
2184
Tao Bao76def242017-11-21 09:25:31 -08002185
Doug Zongker96a57e72010-09-26 14:57:41 -07002186# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002187PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002188 "ext4": "EMMC",
2189 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002190 "f2fs": "EMMC",
2191 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002192}
Doug Zongker96a57e72010-09-26 14:57:41 -07002193
Tao Bao76def242017-11-21 09:25:31 -08002194
Doug Zongker96a57e72010-09-26 14:57:41 -07002195def GetTypeAndDevice(mount_point, info):
2196 fstab = info["fstab"]
2197 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002198 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2199 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002200 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002201 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002202
2203
2204def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002205 """Parses and converts a PEM-encoded certificate into DER-encoded.
2206
2207 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2208
2209 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002210 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002211 """
2212 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002213 save = False
2214 for line in data.split("\n"):
2215 if "--END CERTIFICATE--" in line:
2216 break
2217 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002218 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002219 if "--BEGIN CERTIFICATE--" in line:
2220 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002221 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002222 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002223
Tao Bao04e1f012018-02-04 12:13:35 -08002224
2225def ExtractPublicKey(cert):
2226 """Extracts the public key (PEM-encoded) from the given certificate file.
2227
2228 Args:
2229 cert: The certificate filename.
2230
2231 Returns:
2232 The public key string.
2233
2234 Raises:
2235 AssertionError: On non-zero return from 'openssl'.
2236 """
2237 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2238 # While openssl 1.1 writes the key into the given filename followed by '-out',
2239 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2240 # stdout instead.
2241 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2242 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2243 pubkey, stderrdata = proc.communicate()
2244 assert proc.returncode == 0, \
2245 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2246 return pubkey
2247
2248
Tao Bao1ac886e2019-06-26 11:58:22 -07002249def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002250 """Extracts the AVB public key from the given public or private key.
2251
2252 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002253 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002254 key: The input key file, which should be PEM-encoded public or private key.
2255
2256 Returns:
2257 The path to the extracted AVB public key file.
2258 """
2259 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2260 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002261 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002262 return output
2263
2264
Doug Zongker412c02f2014-02-13 10:58:24 -08002265def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2266 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002267 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002268
Tao Bao6d5d6232018-03-09 17:04:42 -08002269 Most of the space in the boot and recovery images is just the kernel, which is
2270 identical for the two, so the resulting patch should be efficient. Add it to
2271 the output zip, along with a shell script that is run from init.rc on first
2272 boot to actually do the patching and install the new recovery image.
2273
2274 Args:
2275 input_dir: The top-level input directory of the target-files.zip.
2276 output_sink: The callback function that writes the result.
2277 recovery_img: File object for the recovery image.
2278 boot_img: File objects for the boot image.
2279 info_dict: A dict returned by common.LoadInfoDict() on the input
2280 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002281 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002282 if info_dict is None:
2283 info_dict = OPTIONS.info_dict
2284
Tao Bao6d5d6232018-03-09 17:04:42 -08002285 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002286
Tao Baof2cffbd2015-07-22 12:33:18 -07002287 if full_recovery_image:
2288 output_sink("etc/recovery.img", recovery_img.data)
2289
2290 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002291 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002292 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002293 # With system-root-image, boot and recovery images will have mismatching
2294 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2295 # to handle such a case.
2296 if system_root_image:
2297 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002298 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002299 assert not os.path.exists(path)
2300 else:
2301 diff_program = ["imgdiff"]
2302 if os.path.exists(path):
2303 diff_program.append("-b")
2304 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002305 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002306 else:
2307 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002308
2309 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2310 _, _, patch = d.ComputePatch()
2311 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002312
Dan Albertebb19aa2015-03-27 19:11:53 -07002313 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002314 # The following GetTypeAndDevice()s need to use the path in the target
2315 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002316 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2317 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2318 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002319 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002320
Tao Baof2cffbd2015-07-22 12:33:18 -07002321 if full_recovery_image:
2322 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002323if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2324 applypatch \\
2325 --flash /system/etc/recovery.img \\
2326 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2327 log -t recovery "Installing new recovery image: succeeded" || \\
2328 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002329else
2330 log -t recovery "Recovery image already installed"
2331fi
2332""" % {'type': recovery_type,
2333 'device': recovery_device,
2334 'sha1': recovery_img.sha1,
2335 'size': recovery_img.size}
2336 else:
2337 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002338if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2339 applypatch %(bonus_args)s \\
2340 --patch /system/recovery-from-boot.p \\
2341 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2342 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2343 log -t recovery "Installing new recovery image: succeeded" || \\
2344 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002345else
2346 log -t recovery "Recovery image already installed"
2347fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002348""" % {'boot_size': boot_img.size,
2349 'boot_sha1': boot_img.sha1,
2350 'recovery_size': recovery_img.size,
2351 'recovery_sha1': recovery_img.sha1,
2352 'boot_type': boot_type,
2353 'boot_device': boot_device,
2354 'recovery_type': recovery_type,
2355 'recovery_device': recovery_device,
2356 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002357
2358 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002359 # in the L release.
2360 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002361
Tao Bao32fcdab2018-10-12 10:30:39 -07002362 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002363
Tao Baoda30cfa2017-12-01 16:19:46 -08002364 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002365
2366
2367class DynamicPartitionUpdate(object):
2368 def __init__(self, src_group=None, tgt_group=None, progress=None,
2369 block_difference=None):
2370 self.src_group = src_group
2371 self.tgt_group = tgt_group
2372 self.progress = progress
2373 self.block_difference = block_difference
2374
2375 @property
2376 def src_size(self):
2377 if not self.block_difference:
2378 return 0
2379 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2380
2381 @property
2382 def tgt_size(self):
2383 if not self.block_difference:
2384 return 0
2385 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2386
2387 @staticmethod
2388 def _GetSparseImageSize(img):
2389 if not img:
2390 return 0
2391 return img.blocksize * img.total_blocks
2392
2393
2394class DynamicGroupUpdate(object):
2395 def __init__(self, src_size=None, tgt_size=None):
2396 # None: group does not exist. 0: no size limits.
2397 self.src_size = src_size
2398 self.tgt_size = tgt_size
2399
2400
2401class DynamicPartitionsDifference(object):
2402 def __init__(self, info_dict, block_diffs, progress_dict=None,
2403 source_info_dict=None):
2404 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002405 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002406
2407 self._remove_all_before_apply = False
2408 if source_info_dict is None:
2409 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002410 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002411
Tao Baof1113e92019-06-18 12:10:14 -07002412 block_diff_dict = collections.OrderedDict(
2413 [(e.partition, e) for e in block_diffs])
2414
Yifan Hong10c530d2018-12-27 17:34:18 -08002415 assert len(block_diff_dict) == len(block_diffs), \
2416 "Duplicated BlockDifference object for {}".format(
2417 [partition for partition, count in
2418 collections.Counter(e.partition for e in block_diffs).items()
2419 if count > 1])
2420
Yifan Hong79997e52019-01-23 16:56:19 -08002421 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002422
2423 for p, block_diff in block_diff_dict.items():
2424 self._partition_updates[p] = DynamicPartitionUpdate()
2425 self._partition_updates[p].block_difference = block_diff
2426
2427 for p, progress in progress_dict.items():
2428 if p in self._partition_updates:
2429 self._partition_updates[p].progress = progress
2430
2431 tgt_groups = shlex.split(info_dict.get(
2432 "super_partition_groups", "").strip())
2433 src_groups = shlex.split(source_info_dict.get(
2434 "super_partition_groups", "").strip())
2435
2436 for g in tgt_groups:
2437 for p in shlex.split(info_dict.get(
2438 "super_%s_partition_list" % g, "").strip()):
2439 assert p in self._partition_updates, \
2440 "{} is in target super_{}_partition_list but no BlockDifference " \
2441 "object is provided.".format(p, g)
2442 self._partition_updates[p].tgt_group = g
2443
2444 for g in src_groups:
2445 for p in shlex.split(source_info_dict.get(
2446 "super_%s_partition_list" % g, "").strip()):
2447 assert p in self._partition_updates, \
2448 "{} is in source super_{}_partition_list but no BlockDifference " \
2449 "object is provided.".format(p, g)
2450 self._partition_updates[p].src_group = g
2451
Yifan Hong45433e42019-01-18 13:55:25 -08002452 target_dynamic_partitions = set(shlex.split(info_dict.get(
2453 "dynamic_partition_list", "").strip()))
2454 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2455 if u.tgt_size)
2456 assert block_diffs_with_target == target_dynamic_partitions, \
2457 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2458 list(target_dynamic_partitions), list(block_diffs_with_target))
2459
2460 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2461 "dynamic_partition_list", "").strip()))
2462 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2463 if u.src_size)
2464 assert block_diffs_with_source == source_dynamic_partitions, \
2465 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2466 list(source_dynamic_partitions), list(block_diffs_with_source))
2467
Yifan Hong10c530d2018-12-27 17:34:18 -08002468 if self._partition_updates:
2469 logger.info("Updating dynamic partitions %s",
2470 self._partition_updates.keys())
2471
Yifan Hong79997e52019-01-23 16:56:19 -08002472 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002473
2474 for g in tgt_groups:
2475 self._group_updates[g] = DynamicGroupUpdate()
2476 self._group_updates[g].tgt_size = int(info_dict.get(
2477 "super_%s_group_size" % g, "0").strip())
2478
2479 for g in src_groups:
2480 if g not in self._group_updates:
2481 self._group_updates[g] = DynamicGroupUpdate()
2482 self._group_updates[g].src_size = int(source_info_dict.get(
2483 "super_%s_group_size" % g, "0").strip())
2484
2485 self._Compute()
2486
2487 def WriteScript(self, script, output_zip, write_verify_script=False):
2488 script.Comment('--- Start patching dynamic partitions ---')
2489 for p, u in self._partition_updates.items():
2490 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2491 script.Comment('Patch partition %s' % p)
2492 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2493 write_verify_script=False)
2494
2495 op_list_path = MakeTempFile()
2496 with open(op_list_path, 'w') as f:
2497 for line in self._op_list:
2498 f.write('{}\n'.format(line))
2499
2500 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2501
2502 script.Comment('Update dynamic partition metadata')
2503 script.AppendExtra('assert(update_dynamic_partitions('
2504 'package_extract_file("dynamic_partitions_op_list")));')
2505
2506 if write_verify_script:
2507 for p, u in self._partition_updates.items():
2508 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2509 u.block_difference.WritePostInstallVerifyScript(script)
2510 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2511
2512 for p, u in self._partition_updates.items():
2513 if u.tgt_size and u.src_size <= u.tgt_size:
2514 script.Comment('Patch partition %s' % p)
2515 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2516 write_verify_script=write_verify_script)
2517 if write_verify_script:
2518 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2519
2520 script.Comment('--- End patching dynamic partitions ---')
2521
2522 def _Compute(self):
2523 self._op_list = list()
2524
2525 def append(line):
2526 self._op_list.append(line)
2527
2528 def comment(line):
2529 self._op_list.append("# %s" % line)
2530
2531 if self._remove_all_before_apply:
2532 comment('Remove all existing dynamic partitions and groups before '
2533 'applying full OTA')
2534 append('remove_all_groups')
2535
2536 for p, u in self._partition_updates.items():
2537 if u.src_group and not u.tgt_group:
2538 append('remove %s' % p)
2539
2540 for p, u in self._partition_updates.items():
2541 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2542 comment('Move partition %s from %s to default' % (p, u.src_group))
2543 append('move %s default' % p)
2544
2545 for p, u in self._partition_updates.items():
2546 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2547 comment('Shrink partition %s from %d to %d' %
2548 (p, u.src_size, u.tgt_size))
2549 append('resize %s %s' % (p, u.tgt_size))
2550
2551 for g, u in self._group_updates.items():
2552 if u.src_size is not None and u.tgt_size is None:
2553 append('remove_group %s' % g)
2554 if (u.src_size is not None and u.tgt_size is not None and
2555 u.src_size > u.tgt_size):
2556 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2557 append('resize_group %s %d' % (g, u.tgt_size))
2558
2559 for g, u in self._group_updates.items():
2560 if u.src_size is None and u.tgt_size is not None:
2561 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2562 append('add_group %s %d' % (g, u.tgt_size))
2563 if (u.src_size is not None and u.tgt_size is not None and
2564 u.src_size < u.tgt_size):
2565 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2566 append('resize_group %s %d' % (g, u.tgt_size))
2567
2568 for p, u in self._partition_updates.items():
2569 if u.tgt_group and not u.src_group:
2570 comment('Add partition %s to group %s' % (p, u.tgt_group))
2571 append('add %s %s' % (p, u.tgt_group))
2572
2573 for p, u in self._partition_updates.items():
2574 if u.tgt_size and u.src_size < u.tgt_size:
2575 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2576 append('resize %s %d' % (p, u.tgt_size))
2577
2578 for p, u in self._partition_updates.items():
2579 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2580 comment('Move partition %s from default to %s' %
2581 (p, u.tgt_group))
2582 append('move %s %s' % (p, u.tgt_group))