blob: 995b6d3768069249531a6541752e351e290824f9 [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
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070042import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070044
Tao Bao32fcdab2018-10-12 10:30:39 -070045logger = logging.getLogger(__name__)
46
Tao Bao986ee862018-10-04 15:46:16 -070047
Dan Albert8b72aef2015-03-23 19:13:21 -070048class Options(object):
49 def __init__(self):
Pavel Salomatov32676552019-03-06 20:00:45 +030050 base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
51 if base_out_path is None:
52 base_search_path = "out"
53 else:
Tao Bao2cc0ca12019-03-15 10:44:43 -070054 base_search_path = os.path.join(base_out_path,
55 os.path.basename(os.getcwd()))
Pavel Salomatov32676552019-03-06 20:00:45 +030056
Dan Albert8b72aef2015-03-23 19:13:21 -070057 platform_search_path = {
Pavel Salomatov32676552019-03-06 20:00:45 +030058 "linux2": os.path.join(base_search_path, "host/linux-x86"),
59 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070060 }
Doug Zongker85448772014-09-09 14:59:20 -070061
Tao Bao76def242017-11-21 09:25:31 -080062 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070063 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080064 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070065 self.extra_signapk_args = []
66 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080067 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070068 self.public_key_suffix = ".x509.pem"
69 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070070 # use otatools built boot_signer by default
71 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070072 self.boot_signer_args = []
73 self.verity_signer_path = None
74 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.verbose = False
76 self.tempfiles = []
77 self.device_specific = None
78 self.extras = {}
79 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070080 self.source_info_dict = None
81 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070082 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070083 # Stash size cannot exceed cache_size * threshold.
84 self.cache_size = None
85 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070086
87
88OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070089
Tao Bao71197512018-10-11 14:08:45 -070090# The block size that's used across the releasetools scripts.
91BLOCK_SIZE = 4096
92
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080093# Values for "certificate" in apkcerts that mean special things.
94SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
95
Tao Bao5cc0abb2019-03-21 10:18:05 -070096# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
97# that system_other is not in the list because we don't want to include its
98# descriptor into vbmeta.img.
99AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'product_services',
100 'recovery', 'system', 'vendor')
Tao Bao9dd909e2017-11-14 11:27:32 -0800101
Tao Bao08c190f2019-06-03 23:07:58 -0700102# Chained VBMeta partitions.
103AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
104
Tianjie Xu861f4132018-09-12 11:49:33 -0700105# Partitions that should have their care_map added to META/care_map.pb
106PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
107 'odm')
108
109
Tianjie Xu209db462016-05-24 17:34:52 -0700110class ErrorCode(object):
111 """Define error_codes for failures that happen during the actual
112 update package installation.
113
114 Error codes 0-999 are reserved for failures before the package
115 installation (i.e. low battery, package verification failure).
116 Detailed code in 'bootable/recovery/error_code.h' """
117
118 SYSTEM_VERIFICATION_FAILURE = 1000
119 SYSTEM_UPDATE_FAILURE = 1001
120 SYSTEM_UNEXPECTED_CONTENTS = 1002
121 SYSTEM_NONZERO_CONTENTS = 1003
122 SYSTEM_RECOVER_FAILURE = 1004
123 VENDOR_VERIFICATION_FAILURE = 2000
124 VENDOR_UPDATE_FAILURE = 2001
125 VENDOR_UNEXPECTED_CONTENTS = 2002
126 VENDOR_NONZERO_CONTENTS = 2003
127 VENDOR_RECOVER_FAILURE = 2004
128 OEM_PROP_MISMATCH = 3000
129 FINGERPRINT_MISMATCH = 3001
130 THUMBPRINT_MISMATCH = 3002
131 OLDER_BUILD = 3003
132 DEVICE_MISMATCH = 3004
133 BAD_PATCH_FILE = 3005
134 INSUFFICIENT_CACHE_SPACE = 3006
135 TUNE_PARTITION_FAILURE = 3007
136 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800137
Tao Bao80921982018-03-21 21:02:19 -0700138
Dan Albert8b72aef2015-03-23 19:13:21 -0700139class ExternalError(RuntimeError):
140 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700141
142
Tao Bao32fcdab2018-10-12 10:30:39 -0700143def InitLogging():
144 DEFAULT_LOGGING_CONFIG = {
145 'version': 1,
146 'disable_existing_loggers': False,
147 'formatters': {
148 'standard': {
149 'format':
150 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
151 'datefmt': '%Y-%m-%d %H:%M:%S',
152 },
153 },
154 'handlers': {
155 'default': {
156 'class': 'logging.StreamHandler',
157 'formatter': 'standard',
158 },
159 },
160 'loggers': {
161 '': {
162 'handlers': ['default'],
163 'level': 'WARNING',
164 'propagate': True,
165 }
166 }
167 }
168 env_config = os.getenv('LOGGING_CONFIG')
169 if env_config:
170 with open(env_config) as f:
171 config = json.load(f)
172 else:
173 config = DEFAULT_LOGGING_CONFIG
174
175 # Increase the logging level for verbose mode.
176 if OPTIONS.verbose:
177 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
178 config['loggers']['']['level'] = 'INFO'
179
180 logging.config.dictConfig(config)
181
182
Tao Bao39451582017-05-04 11:10:47 -0700183def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700184 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700185
Tao Bao73dd4f42018-10-04 16:25:33 -0700186 Args:
187 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700188 verbose: Whether the commands should be shown. Default to the global
189 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700190 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
191 stdin, etc. stdout and stderr will default to subprocess.PIPE and
192 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800193 universal_newlines will default to True, as most of the users in
194 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700195
196 Returns:
197 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700198 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700199 if 'stdout' not in kwargs and 'stderr' not in kwargs:
200 kwargs['stdout'] = subprocess.PIPE
201 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800202 if 'universal_newlines' not in kwargs:
203 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700204 # Don't log any if caller explicitly says so.
205 if verbose != False:
206 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700207 return subprocess.Popen(args, **kwargs)
208
209
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800210def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800211 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800212
213 Args:
214 args: The command represented as a list of strings.
215 verbose: Whether the commands should be shown. Default to the global
216 verbosity if unspecified.
217 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
218 stdin, etc. stdout and stderr will default to subprocess.PIPE and
219 subprocess.STDOUT respectively unless caller specifies any of them.
220
Bill Peckham889b0c62019-02-21 18:53:37 -0800221 Raises:
222 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800223 """
224 proc = Run(args, verbose=verbose, **kwargs)
225 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800226
227 if proc.returncode != 0:
228 raise ExternalError(
229 "Failed to run command '{}' (exit code {})".format(
230 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800231
232
Tao Bao986ee862018-10-04 15:46:16 -0700233def RunAndCheckOutput(args, verbose=None, **kwargs):
234 """Runs the given command and returns the output.
235
236 Args:
237 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700238 verbose: Whether the commands should be shown. Default to the global
239 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700240 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
241 stdin, etc. stdout and stderr will default to subprocess.PIPE and
242 subprocess.STDOUT respectively unless caller specifies any of them.
243
244 Returns:
245 The output string.
246
247 Raises:
248 ExternalError: On non-zero exit from the command.
249 """
Tao Bao986ee862018-10-04 15:46:16 -0700250 proc = Run(args, verbose=verbose, **kwargs)
251 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700252 # Don't log any if caller explicitly says so.
253 if verbose != False:
254 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700255 if proc.returncode != 0:
256 raise ExternalError(
257 "Failed to run command '{}' (exit code {}):\n{}".format(
258 args, proc.returncode, output))
259 return output
260
261
Tao Baoc765cca2018-01-31 17:32:40 -0800262def RoundUpTo4K(value):
263 rounded_up = value + 4095
264 return rounded_up - (rounded_up % 4096)
265
266
Ying Wang7e6d4e42010-12-13 16:25:36 -0800267def CloseInheritedPipes():
268 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
269 before doing other work."""
270 if platform.system() != "Darwin":
271 return
272 for d in range(3, 1025):
273 try:
274 stat = os.fstat(d)
275 if stat is not None:
276 pipebit = stat[0] & 0x1000
277 if pipebit != 0:
278 os.close(d)
279 except OSError:
280 pass
281
282
Tao Bao410ad8b2018-08-24 12:08:38 -0700283def LoadInfoDict(input_file, repacking=False):
284 """Loads the key/value pairs from the given input target_files.
285
286 It reads `META/misc_info.txt` file in the target_files input, does sanity
287 checks and returns the parsed key/value pairs for to the given build. It's
288 usually called early when working on input target_files files, e.g. when
289 generating OTAs, or signing builds. Note that the function may be called
290 against an old target_files file (i.e. from past dessert releases). So the
291 property parsing needs to be backward compatible.
292
293 In a `META/misc_info.txt`, a few properties are stored as links to the files
294 in the PRODUCT_OUT directory. It works fine with the build system. However,
295 they are no longer available when (re)generating images from target_files zip.
296 When `repacking` is True, redirect these properties to the actual files in the
297 unzipped directory.
298
299 Args:
300 input_file: The input target_files file, which could be an open
301 zipfile.ZipFile instance, or a str for the dir that contains the files
302 unzipped from a target_files file.
303 repacking: Whether it's trying repack an target_files file after loading the
304 info dict (default: False). If so, it will rewrite a few loaded
305 properties (e.g. selinux_fc, root_dir) to point to the actual files in
306 target_files file. When doing repacking, `input_file` must be a dir.
307
308 Returns:
309 A dict that contains the parsed key/value pairs.
310
311 Raises:
312 AssertionError: On invalid input arguments.
313 ValueError: On malformed input values.
314 """
315 if repacking:
316 assert isinstance(input_file, str), \
317 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700318
Doug Zongkerc9253822014-02-04 12:17:58 -0800319 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700320 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800321 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800322 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700323 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800324 try:
325 with open(path) as f:
326 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700327 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800328 if e.errno == errno.ENOENT:
329 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800330
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700331 try:
Michael Runge6e836112014-04-15 17:40:21 -0700332 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700333 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700334 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700335
Tao Bao410ad8b2018-08-24 12:08:38 -0700336 if "recovery_api_version" not in d:
337 raise ValueError("Failed to find 'recovery_api_version'")
338 if "fstab_version" not in d:
339 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800340
Tao Bao410ad8b2018-08-24 12:08:38 -0700341 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700342 # "selinux_fc" properties should point to the file_contexts files
343 # (file_contexts.bin) under META/.
344 for key in d:
345 if key.endswith("selinux_fc"):
346 fc_basename = os.path.basename(d[key])
347 fc_config = os.path.join(input_file, "META", fc_basename)
348 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700349
Daniel Norman72c626f2019-05-13 15:58:14 -0700350 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700351
Tom Cherryd14b8952018-08-09 14:26:00 -0700352 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700353 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700354 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700355 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700356
Tao Baof54216f2016-03-29 15:12:37 -0700357 # Redirect {system,vendor}_base_fs_file.
358 if "system_base_fs_file" in d:
359 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700360 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700361 if os.path.exists(system_base_fs_file):
362 d["system_base_fs_file"] = system_base_fs_file
363 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700364 logger.warning(
365 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700366 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700367
368 if "vendor_base_fs_file" in d:
369 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700370 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700371 if os.path.exists(vendor_base_fs_file):
372 d["vendor_base_fs_file"] = vendor_base_fs_file
373 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700374 logger.warning(
375 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700376 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700377
Doug Zongker37974732010-09-16 17:44:38 -0700378 def makeint(key):
379 if key in d:
380 d[key] = int(d[key], 0)
381
382 makeint("recovery_api_version")
383 makeint("blocksize")
384 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700385 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700386 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700387 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700388 makeint("recovery_size")
389 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800390 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700391
Tao Baoa57ab9f2018-08-24 12:08:38 -0700392 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
393 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
394 # cases, since it may load the info_dict from an old build (e.g. when
395 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800396 system_root_image = d.get("system_root_image") == "true"
397 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700398 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700399 if isinstance(input_file, zipfile.ZipFile):
400 if recovery_fstab_path not in input_file.namelist():
401 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
402 else:
403 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
404 if not os.path.exists(path):
405 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800406 d["fstab"] = LoadRecoveryFSTab(
407 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700408
Tao Bao76def242017-11-21 09:25:31 -0800409 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700410 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700411 if isinstance(input_file, zipfile.ZipFile):
412 if recovery_fstab_path not in input_file.namelist():
413 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
414 else:
415 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
416 if not os.path.exists(path):
417 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800418 d["fstab"] = LoadRecoveryFSTab(
419 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700420
Tianjie Xucfa86222016-03-07 16:31:19 -0800421 else:
422 d["fstab"] = None
423
Tianjie Xu861f4132018-09-12 11:49:33 -0700424 # Tries to load the build props for all partitions with care_map, including
425 # system and vendor.
426 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800427 partition_prop = "{}.build.prop".format(partition)
428 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700429 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800430 # Some partition might use /<partition>/etc/build.prop as the new path.
431 # TODO: try new path first when majority of them switch to the new path.
432 if not d[partition_prop]:
433 d[partition_prop] = LoadBuildProp(
434 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700435 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800436
437 # Set up the salt (based on fingerprint or thumbprint) that will be used when
438 # adding AVB footer.
439 if d.get("avb_enable") == "true":
440 fp = None
441 if "build.prop" in d:
442 build_prop = d["build.prop"]
443 if "ro.build.fingerprint" in build_prop:
444 fp = build_prop["ro.build.fingerprint"]
445 elif "ro.build.thumbprint" in build_prop:
446 fp = build_prop["ro.build.thumbprint"]
447 if fp:
448 d["avb_salt"] = sha256(fp).hexdigest()
449
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700450 return d
451
Tao Baod1de6f32017-03-01 16:38:48 -0800452
Tao Baobcd1d162017-08-26 13:10:26 -0700453def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700454 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700455 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700456 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700457 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700458 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700459 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700460
Tao Baod1de6f32017-03-01 16:38:48 -0800461
Michael Runge6e836112014-04-15 17:40:21 -0700462def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700463 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700464 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700465 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700466 if not line or line.startswith("#"):
467 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700468 if "=" in line:
469 name, value = line.split("=", 1)
470 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700471 return d
472
Tao Baod1de6f32017-03-01 16:38:48 -0800473
Tianjie Xucfa86222016-03-07 16:31:19 -0800474def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
475 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700476 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800477 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700478 self.mount_point = mount_point
479 self.fs_type = fs_type
480 self.device = device
481 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700482 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700483
484 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800485 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700486 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700487 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700488 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700489
Tao Baod1de6f32017-03-01 16:38:48 -0800490 assert fstab_version == 2
491
492 d = {}
493 for line in data.split("\n"):
494 line = line.strip()
495 if not line or line.startswith("#"):
496 continue
497
498 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
499 pieces = line.split()
500 if len(pieces) != 5:
501 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
502
503 # Ignore entries that are managed by vold.
504 options = pieces[4]
505 if "voldmanaged=" in options:
506 continue
507
508 # It's a good line, parse it.
509 length = 0
510 options = options.split(",")
511 for i in options:
512 if i.startswith("length="):
513 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800514 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800515 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700516 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800517
Tao Baod1de6f32017-03-01 16:38:48 -0800518 mount_flags = pieces[3]
519 # Honor the SELinux context if present.
520 context = None
521 for i in mount_flags.split(","):
522 if i.startswith("context="):
523 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800524
Tao Baod1de6f32017-03-01 16:38:48 -0800525 mount_point = pieces[1]
526 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
527 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800528
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700529 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700530 # system. Other areas assume system is always at "/system" so point /system
531 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700532 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800533 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700534 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700535 return d
536
537
Doug Zongker37974732010-09-16 17:44:38 -0700538def DumpInfoDict(d):
539 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700540 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700541
Dan Albert8b72aef2015-03-23 19:13:21 -0700542
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800543def AppendAVBSigningArgs(cmd, partition):
544 """Append signing arguments for avbtool."""
545 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
546 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
547 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
548 if key_path and algorithm:
549 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700550 avb_salt = OPTIONS.info_dict.get("avb_salt")
551 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700552 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700553 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800554
555
Tao Bao02a08592018-07-22 12:40:45 -0700556def GetAvbChainedPartitionArg(partition, info_dict, key=None):
557 """Constructs and returns the arg to build or verify a chained partition.
558
559 Args:
560 partition: The partition name.
561 info_dict: The info dict to look up the key info and rollback index
562 location.
563 key: The key to be used for building or verifying the partition. Defaults to
564 the key listed in info_dict.
565
566 Returns:
567 A string of form "partition:rollback_index_location:key" that can be used to
568 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700569 """
570 if key is None:
571 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao1ac886e2019-06-26 11:58:22 -0700572 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700573 rollback_index_location = info_dict[
574 "avb_" + partition + "_rollback_index_location"]
575 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
576
577
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700578def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800579 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700580 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700581
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700582 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800583 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
584 we are building a two-step special image (i.e. building a recovery image to
585 be loaded into /boot in two-step OTAs).
586
587 Return the image data, or None if sourcedir does not appear to contains files
588 for building the requested image.
589 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700590
591 def make_ramdisk():
592 ramdisk_img = tempfile.NamedTemporaryFile()
593
594 if os.access(fs_config_file, os.F_OK):
595 cmd = ["mkbootfs", "-f", fs_config_file,
596 os.path.join(sourcedir, "RAMDISK")]
597 else:
598 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
599 p1 = Run(cmd, stdout=subprocess.PIPE)
600 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
601
602 p2.wait()
603 p1.wait()
604 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
605 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
606
607 return ramdisk_img
608
609 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
610 return None
611
612 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700613 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700614
Doug Zongkerd5131602012-08-02 14:46:42 -0700615 if info_dict is None:
616 info_dict = OPTIONS.info_dict
617
Doug Zongkereef39442009-04-02 12:14:19 -0700618 img = tempfile.NamedTemporaryFile()
619
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700620 if has_ramdisk:
621 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700622
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800623 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
624 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
625
626 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700627
Benoit Fradina45a8682014-07-14 21:00:43 +0200628 fn = os.path.join(sourcedir, "second")
629 if os.access(fn, os.F_OK):
630 cmd.append("--second")
631 cmd.append(fn)
632
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800633 fn = os.path.join(sourcedir, "dtb")
634 if os.access(fn, os.F_OK):
635 cmd.append("--dtb")
636 cmd.append(fn)
637
Doug Zongker171f1cd2009-06-15 22:36:37 -0700638 fn = os.path.join(sourcedir, "cmdline")
639 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700640 cmd.append("--cmdline")
641 cmd.append(open(fn).read().rstrip("\n"))
642
643 fn = os.path.join(sourcedir, "base")
644 if os.access(fn, os.F_OK):
645 cmd.append("--base")
646 cmd.append(open(fn).read().rstrip("\n"))
647
Ying Wang4de6b5b2010-08-25 14:29:34 -0700648 fn = os.path.join(sourcedir, "pagesize")
649 if os.access(fn, os.F_OK):
650 cmd.append("--pagesize")
651 cmd.append(open(fn).read().rstrip("\n"))
652
Tao Bao76def242017-11-21 09:25:31 -0800653 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700654 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700655 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700656
Tao Bao76def242017-11-21 09:25:31 -0800657 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000658 if args and args.strip():
659 cmd.extend(shlex.split(args))
660
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700661 if has_ramdisk:
662 cmd.extend(["--ramdisk", ramdisk_img.name])
663
Tao Baod95e9fd2015-03-29 23:07:41 -0700664 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800665 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700666 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700667 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700668 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700669 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700670
Tao Baobf70c3182017-07-11 17:27:55 -0700671 # "boot" or "recovery", without extension.
672 partition_name = os.path.basename(sourcedir).lower()
673
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800674 if partition_name == "recovery":
675 if info_dict.get("include_recovery_dtbo") == "true":
676 fn = os.path.join(sourcedir, "recovery_dtbo")
677 cmd.extend(["--recovery_dtbo", fn])
678 if info_dict.get("include_recovery_acpio") == "true":
679 fn = os.path.join(sourcedir, "recovery_acpio")
680 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700681
Tao Bao986ee862018-10-04 15:46:16 -0700682 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700683
Tao Bao76def242017-11-21 09:25:31 -0800684 if (info_dict.get("boot_signer") == "true" and
685 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800686 # Hard-code the path as "/boot" for two-step special recovery image (which
687 # will be loaded into /boot during the two-step OTA).
688 if two_step_image:
689 path = "/boot"
690 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700691 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700692 cmd = [OPTIONS.boot_signer_path]
693 cmd.extend(OPTIONS.boot_signer_args)
694 cmd.extend([path, img.name,
695 info_dict["verity_key"] + ".pk8",
696 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700697 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700698
Tao Baod95e9fd2015-03-29 23:07:41 -0700699 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800700 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700701 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700702 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800703 # We have switched from the prebuilt futility binary to using the tool
704 # (futility-host) built from the source. Override the setting in the old
705 # TF.zip.
706 futility = info_dict["futility"]
707 if futility.startswith("prebuilts/"):
708 futility = "futility-host"
709 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700710 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700711 info_dict["vboot_key"] + ".vbprivk",
712 info_dict["vboot_subkey"] + ".vbprivk",
713 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700714 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700715 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700716
Tao Baof3282b42015-04-01 11:21:55 -0700717 # Clean up the temp files.
718 img_unsigned.close()
719 img_keyblock.close()
720
David Zeuthen8fecb282017-12-01 16:24:01 -0500721 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800722 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700723 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500724 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400725 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700726 "--partition_size", str(part_size), "--partition_name",
727 partition_name]
728 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500729 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400730 if args and args.strip():
731 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700732 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500733
734 img.seek(os.SEEK_SET, 0)
735 data = img.read()
736
737 if has_ramdisk:
738 ramdisk_img.close()
739 img.close()
740
741 return data
742
743
Doug Zongkerd5131602012-08-02 14:46:42 -0700744def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800745 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700746 """Return a File object with the desired bootable image.
747
748 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
749 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
750 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700751
Doug Zongker55d93282011-01-25 17:03:34 -0800752 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
753 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700754 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800755 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700756
757 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
758 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700759 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700760 return File.FromLocalFile(name, prebuilt_path)
761
Tao Bao32fcdab2018-10-12 10:30:39 -0700762 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700763
764 if info_dict is None:
765 info_dict = OPTIONS.info_dict
766
767 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800768 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
769 # for recovery.
770 has_ramdisk = (info_dict.get("system_root_image") != "true" or
771 prebuilt_name != "boot.img" or
772 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700773
Doug Zongker6f1d0312014-08-22 08:07:12 -0700774 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400775 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
776 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800777 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700778 if data:
779 return File(name, data)
780 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800781
Doug Zongkereef39442009-04-02 12:14:19 -0700782
Narayan Kamatha07bf042017-08-14 14:49:21 +0100783def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800784 """Gunzips the given gzip compressed file to a given output file."""
785 with gzip.open(in_filename, "rb") as in_file, \
786 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100787 shutil.copyfileobj(in_file, out_file)
788
789
Tao Bao0ff15de2019-03-20 11:26:06 -0700790def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800791 """Unzips the archive to the given directory.
792
793 Args:
794 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800795 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700796 patterns: Files to unzip from the archive. If omitted, will unzip the entire
797 archvie. Non-matching patterns will be filtered out. If there's no match
798 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800799 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800800 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700801 if patterns is not None:
802 # Filter out non-matching patterns. unzip will complain otherwise.
803 with zipfile.ZipFile(filename) as input_zip:
804 names = input_zip.namelist()
805 filtered = [
806 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
807
808 # There isn't any matching files. Don't unzip anything.
809 if not filtered:
810 return
811 cmd.extend(filtered)
812
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800813 RunAndCheckOutput(cmd)
814
815
Doug Zongker75f17362009-12-08 13:46:44 -0800816def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800817 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800818
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800819 Args:
820 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
821 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
822
823 pattern: Files to unzip from the archive. If omitted, will unzip the entire
824 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800825
Tao Bao1c830bf2017-12-25 10:43:47 -0800826 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800827 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800828 """
Doug Zongkereef39442009-04-02 12:14:19 -0700829
Tao Bao1c830bf2017-12-25 10:43:47 -0800830 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800831 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
832 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800833 UnzipToDir(m.group(1), tmp, pattern)
834 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800835 filename = m.group(1)
836 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800837 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800838
Tao Baodba59ee2018-01-09 13:21:02 -0800839 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700840
841
Yifan Hong8a66a712019-04-04 15:37:57 -0700842def GetUserImage(which, tmpdir, input_zip,
843 info_dict=None,
844 allow_shared_blocks=None,
845 hashtree_info_generator=None,
846 reset_file_map=False):
847 """Returns an Image object suitable for passing to BlockImageDiff.
848
849 This function loads the specified image from the given path. If the specified
850 image is sparse, it also performs additional processing for OTA purpose. For
851 example, it always adds block 0 to clobbered blocks list. It also detects
852 files that cannot be reconstructed from the block list, for whom we should
853 avoid applying imgdiff.
854
855 Args:
856 which: The partition name.
857 tmpdir: The directory that contains the prebuilt image and block map file.
858 input_zip: The target-files ZIP archive.
859 info_dict: The dict to be looked up for relevant info.
860 allow_shared_blocks: If image is sparse, whether having shared blocks is
861 allowed. If none, it is looked up from info_dict.
862 hashtree_info_generator: If present and image is sparse, generates the
863 hashtree_info for this sparse image.
864 reset_file_map: If true and image is sparse, reset file map before returning
865 the image.
866 Returns:
867 A Image object. If it is a sparse image and reset_file_map is False, the
868 image will have file_map info loaded.
869 """
Tao Baoc1a1ec32019-06-18 16:29:37 -0700870 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -0700871 info_dict = LoadInfoDict(input_zip)
872
873 is_sparse = info_dict.get("extfs_sparse_flag")
874
875 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
876 # shared blocks (i.e. some blocks will show up in multiple files' block
877 # list). We can only allocate such shared blocks to the first "owner", and
878 # disable imgdiff for all later occurrences.
879 if allow_shared_blocks is None:
880 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
881
882 if is_sparse:
883 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
884 hashtree_info_generator)
885 if reset_file_map:
886 img.ResetFileMap()
887 return img
888 else:
889 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
890
891
892def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
893 """Returns a Image object suitable for passing to BlockImageDiff.
894
895 This function loads the specified non-sparse image from the given path.
896
897 Args:
898 which: The partition name.
899 tmpdir: The directory that contains the prebuilt image and block map file.
900 Returns:
901 A Image object.
902 """
903 path = os.path.join(tmpdir, "IMAGES", which + ".img")
904 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
905
906 # The image and map files must have been created prior to calling
907 # ota_from_target_files.py (since LMP).
908 assert os.path.exists(path) and os.path.exists(mappath)
909
910 return blockimgdiff.FileImage(path, hashtree_info_generator=
911 hashtree_info_generator)
912
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700913def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
914 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800915 """Returns a SparseImage object suitable for passing to BlockImageDiff.
916
917 This function loads the specified sparse image from the given path, and
918 performs additional processing for OTA purpose. For example, it always adds
919 block 0 to clobbered blocks list. It also detects files that cannot be
920 reconstructed from the block list, for whom we should avoid applying imgdiff.
921
922 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700923 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800924 tmpdir: The directory that contains the prebuilt image and block map file.
925 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800926 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700927 hashtree_info_generator: If present, generates the hashtree_info for this
928 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800929 Returns:
930 A SparseImage object, with file_map info loaded.
931 """
Tao Baoc765cca2018-01-31 17:32:40 -0800932 path = os.path.join(tmpdir, "IMAGES", which + ".img")
933 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
934
935 # The image and map files must have been created prior to calling
936 # ota_from_target_files.py (since LMP).
937 assert os.path.exists(path) and os.path.exists(mappath)
938
939 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
940 # it to clobbered_blocks so that it will be written to the target
941 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
942 clobbered_blocks = "0"
943
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700944 image = sparse_img.SparseImage(
945 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
946 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800947
948 # block.map may contain less blocks, because mke2fs may skip allocating blocks
949 # if they contain all zeros. We can't reconstruct such a file from its block
950 # list. Tag such entries accordingly. (Bug: 65213616)
951 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800952 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700953 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800954 continue
955
Tom Cherryd14b8952018-08-09 14:26:00 -0700956 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
957 # filename listed in system.map may contain an additional leading slash
958 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
959 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -0800960 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -0700961
Tom Cherryd14b8952018-08-09 14:26:00 -0700962 # Special handling another case, where files not under /system
963 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700964 if which == 'system' and not arcname.startswith('SYSTEM'):
965 arcname = 'ROOT/' + arcname
966
967 assert arcname in input_zip.namelist(), \
968 "Failed to find the ZIP entry for {}".format(entry)
969
Tao Baoc765cca2018-01-31 17:32:40 -0800970 info = input_zip.getinfo(arcname)
971 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800972
973 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800974 # image, check the original block list to determine its completeness. Note
975 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800976 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800977 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800978
Tao Baoc765cca2018-01-31 17:32:40 -0800979 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
980 ranges.extra['incomplete'] = True
981
982 return image
983
984
Doug Zongkereef39442009-04-02 12:14:19 -0700985def GetKeyPasswords(keylist):
986 """Given a list of keys, prompt the user to enter passwords for
987 those which require them. Return a {key: password} dict. password
988 will be None if the key has no password."""
989
Doug Zongker8ce7c252009-05-22 13:34:54 -0700990 no_passwords = []
991 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700992 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700993 devnull = open("/dev/null", "w+b")
994 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800995 # We don't need a password for things that aren't really keys.
996 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700997 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700998 continue
999
T.R. Fullhart37e10522013-03-18 10:31:26 -07001000 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001001 "-inform", "DER", "-nocrypt"],
1002 stdin=devnull.fileno(),
1003 stdout=devnull.fileno(),
1004 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001005 p.communicate()
1006 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001007 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001008 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001009 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001010 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1011 "-inform", "DER", "-passin", "pass:"],
1012 stdin=devnull.fileno(),
1013 stdout=devnull.fileno(),
1014 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001015 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001016 if p.returncode == 0:
1017 # Encrypted key with empty string as password.
1018 key_passwords[k] = ''
1019 elif stderr.startswith('Error decrypting key'):
1020 # Definitely encrypted key.
1021 # It would have said "Error reading key" if it didn't parse correctly.
1022 need_passwords.append(k)
1023 else:
1024 # Potentially, a type of key that openssl doesn't understand.
1025 # We'll let the routines in signapk.jar handle it.
1026 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001027 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001028
T.R. Fullhart37e10522013-03-18 10:31:26 -07001029 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001030 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001031 return key_passwords
1032
1033
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001034def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001035 """Gets the minSdkVersion declared in the APK.
1036
1037 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1038 This can be both a decimal number (API Level) or a codename.
1039
1040 Args:
1041 apk_name: The APK filename.
1042
1043 Returns:
1044 The parsed SDK version string.
1045
1046 Raises:
1047 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001048 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001049 proc = Run(
1050 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1051 stderr=subprocess.PIPE)
1052 stdoutdata, stderrdata = proc.communicate()
1053 if proc.returncode != 0:
1054 raise ExternalError(
1055 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1056 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001057
Tao Baof47bf0f2018-03-21 23:28:51 -07001058 for line in stdoutdata.split("\n"):
1059 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001060 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1061 if m:
1062 return m.group(1)
1063 raise ExternalError("No minSdkVersion returned by aapt")
1064
1065
1066def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001067 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001068
Tao Baof47bf0f2018-03-21 23:28:51 -07001069 If minSdkVersion is set to a codename, it is translated to a number using the
1070 provided map.
1071
1072 Args:
1073 apk_name: The APK filename.
1074
1075 Returns:
1076 The parsed SDK version number.
1077
1078 Raises:
1079 ExternalError: On failing to get the min SDK version number.
1080 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001081 version = GetMinSdkVersion(apk_name)
1082 try:
1083 return int(version)
1084 except ValueError:
1085 # Not a decimal number. Codename?
1086 if version in codename_to_api_level_map:
1087 return codename_to_api_level_map[version]
1088 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001089 raise ExternalError(
1090 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1091 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001092
1093
1094def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001095 codename_to_api_level_map=None, whole_file=False,
1096 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001097 """Sign the input_name zip/jar/apk, producing output_name. Use the
1098 given key and password (the latter may be None if the key does not
1099 have a password.
1100
Doug Zongker951495f2009-08-14 12:44:19 -07001101 If whole_file is true, use the "-w" option to SignApk to embed a
1102 signature that covers the whole file in the archive comment of the
1103 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001104
1105 min_api_level is the API Level (int) of the oldest platform this file may end
1106 up on. If not specified for an APK, the API Level is obtained by interpreting
1107 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1108
1109 codename_to_api_level_map is needed to translate the codename which may be
1110 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001111
1112 Caller may optionally specify extra args to be passed to SignApk, which
1113 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001114 """
Tao Bao76def242017-11-21 09:25:31 -08001115 if codename_to_api_level_map is None:
1116 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001117 if extra_signapk_args is None:
1118 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001119
Alex Klyubin9667b182015-12-10 13:38:50 -08001120 java_library_path = os.path.join(
1121 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1122
Tao Baoe95540e2016-11-08 12:08:53 -08001123 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1124 ["-Djava.library.path=" + java_library_path,
1125 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001126 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001127 if whole_file:
1128 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001129
1130 min_sdk_version = min_api_level
1131 if min_sdk_version is None:
1132 if not whole_file:
1133 min_sdk_version = GetMinSdkVersionInt(
1134 input_name, codename_to_api_level_map)
1135 if min_sdk_version is not None:
1136 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1137
T.R. Fullhart37e10522013-03-18 10:31:26 -07001138 cmd.extend([key + OPTIONS.public_key_suffix,
1139 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001140 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001141
Tao Bao73dd4f42018-10-04 16:25:33 -07001142 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001143 if password is not None:
1144 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001145 stdoutdata, _ = proc.communicate(password)
1146 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001147 raise ExternalError(
1148 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001149 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001150
Doug Zongkereef39442009-04-02 12:14:19 -07001151
Doug Zongker37974732010-09-16 17:44:38 -07001152def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001153 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001154
Tao Bao9dd909e2017-11-14 11:27:32 -08001155 For non-AVB images, raise exception if the data is too big. Print a warning
1156 if the data is nearing the maximum size.
1157
1158 For AVB images, the actual image size should be identical to the limit.
1159
1160 Args:
1161 data: A string that contains all the data for the partition.
1162 target: The partition name. The ".img" suffix is optional.
1163 info_dict: The dict to be looked up for relevant info.
1164 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001165 if target.endswith(".img"):
1166 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001167 mount_point = "/" + target
1168
Ying Wangf8824af2014-06-03 14:07:27 -07001169 fs_type = None
1170 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001171 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001172 if mount_point == "/userdata":
1173 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001174 p = info_dict["fstab"][mount_point]
1175 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001176 device = p.device
1177 if "/" in device:
1178 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001179 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001180 if not fs_type or not limit:
1181 return
Doug Zongkereef39442009-04-02 12:14:19 -07001182
Andrew Boie0f9aec82012-02-14 09:32:52 -08001183 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001184 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1185 # path.
1186 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1187 if size != limit:
1188 raise ExternalError(
1189 "Mismatching image size for %s: expected %d actual %d" % (
1190 target, limit, size))
1191 else:
1192 pct = float(size) * 100.0 / limit
1193 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1194 if pct >= 99.0:
1195 raise ExternalError(msg)
1196 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001197 logger.warning("\n WARNING: %s\n", msg)
1198 else:
1199 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001200
1201
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001202def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001203 """Parses the APK certs info from a given target-files zip.
1204
1205 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1206 tuple with the following elements: (1) a dictionary that maps packages to
1207 certs (based on the "certificate" and "private_key" attributes in the file;
1208 (2) a string representing the extension of compressed APKs in the target files
1209 (e.g ".gz", ".bro").
1210
1211 Args:
1212 tf_zip: The input target_files ZipFile (already open).
1213
1214 Returns:
1215 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1216 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1217 no compressed APKs.
1218 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001219 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001220 compressed_extension = None
1221
Tao Bao0f990332017-09-08 19:02:54 -07001222 # META/apkcerts.txt contains the info for _all_ the packages known at build
1223 # time. Filter out the ones that are not installed.
1224 installed_files = set()
1225 for name in tf_zip.namelist():
1226 basename = os.path.basename(name)
1227 if basename:
1228 installed_files.add(basename)
1229
Tao Baoda30cfa2017-12-01 16:19:46 -08001230 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001231 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001232 if not line:
1233 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001234 m = re.match(
1235 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1236 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1237 line)
1238 if not m:
1239 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001240
Tao Bao818ddf52018-01-05 11:17:34 -08001241 matches = m.groupdict()
1242 cert = matches["CERT"]
1243 privkey = matches["PRIVKEY"]
1244 name = matches["NAME"]
1245 this_compressed_extension = matches["COMPRESSED"]
1246
1247 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1248 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1249 if cert in SPECIAL_CERT_STRINGS and not privkey:
1250 certmap[name] = cert
1251 elif (cert.endswith(OPTIONS.public_key_suffix) and
1252 privkey.endswith(OPTIONS.private_key_suffix) and
1253 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1254 certmap[name] = cert[:-public_key_suffix_len]
1255 else:
1256 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1257
1258 if not this_compressed_extension:
1259 continue
1260
1261 # Only count the installed files.
1262 filename = name + '.' + this_compressed_extension
1263 if filename not in installed_files:
1264 continue
1265
1266 # Make sure that all the values in the compression map have the same
1267 # extension. We don't support multiple compression methods in the same
1268 # system image.
1269 if compressed_extension:
1270 if this_compressed_extension != compressed_extension:
1271 raise ValueError(
1272 "Multiple compressed extensions: {} vs {}".format(
1273 compressed_extension, this_compressed_extension))
1274 else:
1275 compressed_extension = this_compressed_extension
1276
1277 return (certmap,
1278 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001279
1280
Doug Zongkereef39442009-04-02 12:14:19 -07001281COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001282Global options
1283
1284 -p (--path) <dir>
1285 Prepend <dir>/bin to the list of places to search for binaries run by this
1286 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001287
Doug Zongker05d3dea2009-06-22 11:32:31 -07001288 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001289 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001290
Tao Bao30df8b42018-04-23 15:32:53 -07001291 -x (--extra) <key=value>
1292 Add a key/value pair to the 'extras' dict, which device-specific extension
1293 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001294
Doug Zongkereef39442009-04-02 12:14:19 -07001295 -v (--verbose)
1296 Show command lines being executed.
1297
1298 -h (--help)
1299 Display this usage message and exit.
1300"""
1301
1302def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001303 print(docstring.rstrip("\n"))
1304 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001305
1306
1307def ParseOptions(argv,
1308 docstring,
1309 extra_opts="", extra_long_opts=(),
1310 extra_option_handler=None):
1311 """Parse the options in argv and return any arguments that aren't
1312 flags. docstring is the calling module's docstring, to be displayed
1313 for errors and -h. extra_opts and extra_long_opts are for flags
1314 defined by the caller, which are processed by passing them to
1315 extra_option_handler."""
1316
1317 try:
1318 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001319 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001320 ["help", "verbose", "path=", "signapk_path=",
1321 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001322 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001323 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1324 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001325 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001326 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001327 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001328 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001329 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001330 sys.exit(2)
1331
Doug Zongkereef39442009-04-02 12:14:19 -07001332 for o, a in opts:
1333 if o in ("-h", "--help"):
1334 Usage(docstring)
1335 sys.exit()
1336 elif o in ("-v", "--verbose"):
1337 OPTIONS.verbose = True
1338 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001339 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001340 elif o in ("--signapk_path",):
1341 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001342 elif o in ("--signapk_shared_library_path",):
1343 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001344 elif o in ("--extra_signapk_args",):
1345 OPTIONS.extra_signapk_args = shlex.split(a)
1346 elif o in ("--java_path",):
1347 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001348 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001349 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001350 elif o in ("--public_key_suffix",):
1351 OPTIONS.public_key_suffix = a
1352 elif o in ("--private_key_suffix",):
1353 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001354 elif o in ("--boot_signer_path",):
1355 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001356 elif o in ("--boot_signer_args",):
1357 OPTIONS.boot_signer_args = shlex.split(a)
1358 elif o in ("--verity_signer_path",):
1359 OPTIONS.verity_signer_path = a
1360 elif o in ("--verity_signer_args",):
1361 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001362 elif o in ("-s", "--device_specific"):
1363 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001364 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001365 key, value = a.split("=", 1)
1366 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001367 else:
1368 if extra_option_handler is None or not extra_option_handler(o, a):
1369 assert False, "unknown option \"%s\"" % (o,)
1370
Doug Zongker85448772014-09-09 14:59:20 -07001371 if OPTIONS.search_path:
1372 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1373 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001374
1375 return args
1376
1377
Tao Bao4c851b12016-09-19 13:54:38 -07001378def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001379 """Make a temp file and add it to the list of things to be deleted
1380 when Cleanup() is called. Return the filename."""
1381 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1382 os.close(fd)
1383 OPTIONS.tempfiles.append(fn)
1384 return fn
1385
1386
Tao Bao1c830bf2017-12-25 10:43:47 -08001387def MakeTempDir(prefix='tmp', suffix=''):
1388 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1389
1390 Returns:
1391 The absolute pathname of the new directory.
1392 """
1393 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1394 OPTIONS.tempfiles.append(dir_name)
1395 return dir_name
1396
1397
Doug Zongkereef39442009-04-02 12:14:19 -07001398def Cleanup():
1399 for i in OPTIONS.tempfiles:
1400 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001401 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001402 else:
1403 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001404 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001405
1406
1407class PasswordManager(object):
1408 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001409 self.editor = os.getenv("EDITOR")
1410 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001411
1412 def GetPasswords(self, items):
1413 """Get passwords corresponding to each string in 'items',
1414 returning a dict. (The dict may have keys in addition to the
1415 values in 'items'.)
1416
1417 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1418 user edit that file to add more needed passwords. If no editor is
1419 available, or $ANDROID_PW_FILE isn't define, prompts the user
1420 interactively in the ordinary way.
1421 """
1422
1423 current = self.ReadFile()
1424
1425 first = True
1426 while True:
1427 missing = []
1428 for i in items:
1429 if i not in current or not current[i]:
1430 missing.append(i)
1431 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001432 if not missing:
1433 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001434
1435 for i in missing:
1436 current[i] = ""
1437
1438 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001439 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001440 if sys.version_info[0] >= 3:
1441 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001442 answer = raw_input("try to edit again? [y]> ").strip()
1443 if answer and answer[0] not in 'yY':
1444 raise RuntimeError("key passwords unavailable")
1445 first = False
1446
1447 current = self.UpdateAndReadFile(current)
1448
Dan Albert8b72aef2015-03-23 19:13:21 -07001449 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001450 """Prompt the user to enter a value (password) for each key in
1451 'current' whose value is fales. Returns a new dict with all the
1452 values.
1453 """
1454 result = {}
1455 for k, v in sorted(current.iteritems()):
1456 if v:
1457 result[k] = v
1458 else:
1459 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001460 result[k] = getpass.getpass(
1461 "Enter password for %s key> " % k).strip()
1462 if result[k]:
1463 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001464 return result
1465
1466 def UpdateAndReadFile(self, current):
1467 if not self.editor or not self.pwfile:
1468 return self.PromptResult(current)
1469
1470 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001471 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001472 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1473 f.write("# (Additional spaces are harmless.)\n\n")
1474
1475 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001476 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1477 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001478 f.write("[[[ %s ]]] %s\n" % (v, k))
1479 if not v and first_line is None:
1480 # position cursor on first line with no password.
1481 first_line = i + 4
1482 f.close()
1483
Tao Bao986ee862018-10-04 15:46:16 -07001484 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001485
1486 return self.ReadFile()
1487
1488 def ReadFile(self):
1489 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001490 if self.pwfile is None:
1491 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001492 try:
1493 f = open(self.pwfile, "r")
1494 for line in f:
1495 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001496 if not line or line[0] == '#':
1497 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001498 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1499 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001500 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001501 else:
1502 result[m.group(2)] = m.group(1)
1503 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001504 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001505 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001506 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001507 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001508
1509
Dan Albert8e0178d2015-01-27 15:53:15 -08001510def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1511 compress_type=None):
1512 import datetime
1513
1514 # http://b/18015246
1515 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1516 # for files larger than 2GiB. We can work around this by adjusting their
1517 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1518 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1519 # it isn't clear to me exactly what circumstances cause this).
1520 # `zipfile.write()` must be used directly to work around this.
1521 #
1522 # This mess can be avoided if we port to python3.
1523 saved_zip64_limit = zipfile.ZIP64_LIMIT
1524 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1525
1526 if compress_type is None:
1527 compress_type = zip_file.compression
1528 if arcname is None:
1529 arcname = filename
1530
1531 saved_stat = os.stat(filename)
1532
1533 try:
1534 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1535 # file to be zipped and reset it when we're done.
1536 os.chmod(filename, perms)
1537
1538 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001539 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1540 # intentional. zip stores datetimes in local time without a time zone
1541 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1542 # in the zip archive.
1543 local_epoch = datetime.datetime.fromtimestamp(0)
1544 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001545 os.utime(filename, (timestamp, timestamp))
1546
1547 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1548 finally:
1549 os.chmod(filename, saved_stat.st_mode)
1550 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1551 zipfile.ZIP64_LIMIT = saved_zip64_limit
1552
1553
Tao Bao58c1b962015-05-20 09:32:18 -07001554def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001555 compress_type=None):
1556 """Wrap zipfile.writestr() function to work around the zip64 limit.
1557
1558 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1559 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1560 when calling crc32(bytes).
1561
1562 But it still works fine to write a shorter string into a large zip file.
1563 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1564 when we know the string won't be too long.
1565 """
1566
1567 saved_zip64_limit = zipfile.ZIP64_LIMIT
1568 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1569
1570 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1571 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001572 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001573 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001574 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001575 else:
Tao Baof3282b42015-04-01 11:21:55 -07001576 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001577 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1578 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1579 # such a case (since
1580 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1581 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1582 # permission bits. We follow the logic in Python 3 to get consistent
1583 # behavior between using the two versions.
1584 if not zinfo.external_attr:
1585 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001586
1587 # If compress_type is given, it overrides the value in zinfo.
1588 if compress_type is not None:
1589 zinfo.compress_type = compress_type
1590
Tao Bao58c1b962015-05-20 09:32:18 -07001591 # If perms is given, it has a priority.
1592 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001593 # If perms doesn't set the file type, mark it as a regular file.
1594 if perms & 0o770000 == 0:
1595 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001596 zinfo.external_attr = perms << 16
1597
Tao Baof3282b42015-04-01 11:21:55 -07001598 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001599 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1600
Dan Albert8b72aef2015-03-23 19:13:21 -07001601 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001602 zipfile.ZIP64_LIMIT = saved_zip64_limit
1603
1604
Tao Bao89d7ab22017-12-14 17:05:33 -08001605def ZipDelete(zip_filename, entries):
1606 """Deletes entries from a ZIP file.
1607
1608 Since deleting entries from a ZIP file is not supported, it shells out to
1609 'zip -d'.
1610
1611 Args:
1612 zip_filename: The name of the ZIP file.
1613 entries: The name of the entry, or the list of names to be deleted.
1614
1615 Raises:
1616 AssertionError: In case of non-zero return from 'zip'.
1617 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001618 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001619 entries = [entries]
1620 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001621 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001622
1623
Tao Baof3282b42015-04-01 11:21:55 -07001624def ZipClose(zip_file):
1625 # http://b/18015246
1626 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1627 # central directory.
1628 saved_zip64_limit = zipfile.ZIP64_LIMIT
1629 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1630
1631 zip_file.close()
1632
1633 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001634
1635
1636class DeviceSpecificParams(object):
1637 module = None
1638 def __init__(self, **kwargs):
1639 """Keyword arguments to the constructor become attributes of this
1640 object, which is passed to all functions in the device-specific
1641 module."""
1642 for k, v in kwargs.iteritems():
1643 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001644 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001645
1646 if self.module is None:
1647 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001648 if not path:
1649 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001650 try:
1651 if os.path.isdir(path):
1652 info = imp.find_module("releasetools", [path])
1653 else:
1654 d, f = os.path.split(path)
1655 b, x = os.path.splitext(f)
1656 if x == ".py":
1657 f = b
1658 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001659 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001660 self.module = imp.load_module("device_specific", *info)
1661 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001662 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001663
1664 def _DoCall(self, function_name, *args, **kwargs):
1665 """Call the named function in the device-specific module, passing
1666 the given args and kwargs. The first argument to the call will be
1667 the DeviceSpecific object itself. If there is no module, or the
1668 module does not define the function, return the value of the
1669 'default' kwarg (which itself defaults to None)."""
1670 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001671 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001672 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1673
1674 def FullOTA_Assertions(self):
1675 """Called after emitting the block of assertions at the top of a
1676 full OTA package. Implementations can add whatever additional
1677 assertions they like."""
1678 return self._DoCall("FullOTA_Assertions")
1679
Doug Zongkere5ff5902012-01-17 10:55:37 -08001680 def FullOTA_InstallBegin(self):
1681 """Called at the start of full OTA installation."""
1682 return self._DoCall("FullOTA_InstallBegin")
1683
Yifan Hong10c530d2018-12-27 17:34:18 -08001684 def FullOTA_GetBlockDifferences(self):
1685 """Called during full OTA installation and verification.
1686 Implementation should return a list of BlockDifference objects describing
1687 the update on each additional partitions.
1688 """
1689 return self._DoCall("FullOTA_GetBlockDifferences")
1690
Doug Zongker05d3dea2009-06-22 11:32:31 -07001691 def FullOTA_InstallEnd(self):
1692 """Called at the end of full OTA installation; typically this is
1693 used to install the image for the device's baseband processor."""
1694 return self._DoCall("FullOTA_InstallEnd")
1695
1696 def IncrementalOTA_Assertions(self):
1697 """Called after emitting the block of assertions at the top of an
1698 incremental OTA package. Implementations can add whatever
1699 additional assertions they like."""
1700 return self._DoCall("IncrementalOTA_Assertions")
1701
Doug Zongkere5ff5902012-01-17 10:55:37 -08001702 def IncrementalOTA_VerifyBegin(self):
1703 """Called at the start of the verification phase of incremental
1704 OTA installation; additional checks can be placed here to abort
1705 the script before any changes are made."""
1706 return self._DoCall("IncrementalOTA_VerifyBegin")
1707
Doug Zongker05d3dea2009-06-22 11:32:31 -07001708 def IncrementalOTA_VerifyEnd(self):
1709 """Called at the end of the verification phase of incremental OTA
1710 installation; additional checks can be placed here to abort the
1711 script before any changes are made."""
1712 return self._DoCall("IncrementalOTA_VerifyEnd")
1713
Doug Zongkere5ff5902012-01-17 10:55:37 -08001714 def IncrementalOTA_InstallBegin(self):
1715 """Called at the start of incremental OTA installation (after
1716 verification is complete)."""
1717 return self._DoCall("IncrementalOTA_InstallBegin")
1718
Yifan Hong10c530d2018-12-27 17:34:18 -08001719 def IncrementalOTA_GetBlockDifferences(self):
1720 """Called during incremental OTA installation and verification.
1721 Implementation should return a list of BlockDifference objects describing
1722 the update on each additional partitions.
1723 """
1724 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1725
Doug Zongker05d3dea2009-06-22 11:32:31 -07001726 def IncrementalOTA_InstallEnd(self):
1727 """Called at the end of incremental OTA installation; typically
1728 this is used to install the image for the device's baseband
1729 processor."""
1730 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001731
Tao Bao9bc6bb22015-11-09 16:58:28 -08001732 def VerifyOTA_Assertions(self):
1733 return self._DoCall("VerifyOTA_Assertions")
1734
Tao Bao76def242017-11-21 09:25:31 -08001735
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001736class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001737 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001738 self.name = name
1739 self.data = data
1740 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001741 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001742 self.sha1 = sha1(data).hexdigest()
1743
1744 @classmethod
1745 def FromLocalFile(cls, name, diskname):
1746 f = open(diskname, "rb")
1747 data = f.read()
1748 f.close()
1749 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001750
1751 def WriteToTemp(self):
1752 t = tempfile.NamedTemporaryFile()
1753 t.write(self.data)
1754 t.flush()
1755 return t
1756
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001757 def WriteToDir(self, d):
1758 with open(os.path.join(d, self.name), "wb") as fp:
1759 fp.write(self.data)
1760
Geremy Condra36bd3652014-02-06 19:45:10 -08001761 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001762 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001763
Tao Bao76def242017-11-21 09:25:31 -08001764
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001765DIFF_PROGRAM_BY_EXT = {
1766 ".gz" : "imgdiff",
1767 ".zip" : ["imgdiff", "-z"],
1768 ".jar" : ["imgdiff", "-z"],
1769 ".apk" : ["imgdiff", "-z"],
1770 ".img" : "imgdiff",
1771 }
1772
Tao Bao76def242017-11-21 09:25:31 -08001773
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001774class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001775 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001776 self.tf = tf
1777 self.sf = sf
1778 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001779 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001780
1781 def ComputePatch(self):
1782 """Compute the patch (as a string of data) needed to turn sf into
1783 tf. Returns the same tuple as GetPatch()."""
1784
1785 tf = self.tf
1786 sf = self.sf
1787
Doug Zongker24cd2802012-08-14 16:36:15 -07001788 if self.diff_program:
1789 diff_program = self.diff_program
1790 else:
1791 ext = os.path.splitext(tf.name)[1]
1792 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001793
1794 ttemp = tf.WriteToTemp()
1795 stemp = sf.WriteToTemp()
1796
1797 ext = os.path.splitext(tf.name)[1]
1798
1799 try:
1800 ptemp = tempfile.NamedTemporaryFile()
1801 if isinstance(diff_program, list):
1802 cmd = copy.copy(diff_program)
1803 else:
1804 cmd = [diff_program]
1805 cmd.append(stemp.name)
1806 cmd.append(ttemp.name)
1807 cmd.append(ptemp.name)
1808 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001809 err = []
1810 def run():
1811 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001812 if e:
1813 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001814 th = threading.Thread(target=run)
1815 th.start()
1816 th.join(timeout=300) # 5 mins
1817 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001818 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001819 p.terminate()
1820 th.join(5)
1821 if th.is_alive():
1822 p.kill()
1823 th.join()
1824
Tianjie Xua2a9f992018-01-05 15:15:54 -08001825 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001826 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001827 self.patch = None
1828 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001829 diff = ptemp.read()
1830 finally:
1831 ptemp.close()
1832 stemp.close()
1833 ttemp.close()
1834
1835 self.patch = diff
1836 return self.tf, self.sf, self.patch
1837
1838
1839 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001840 """Returns a tuple of (target_file, source_file, patch_data).
1841
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001842 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001843 computing the patch failed.
1844 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001845 return self.tf, self.sf, self.patch
1846
1847
1848def ComputeDifferences(diffs):
1849 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001850 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001851
1852 # Do the largest files first, to try and reduce the long-pole effect.
1853 by_size = [(i.tf.size, i) for i in diffs]
1854 by_size.sort(reverse=True)
1855 by_size = [i[1] for i in by_size]
1856
1857 lock = threading.Lock()
1858 diff_iter = iter(by_size) # accessed under lock
1859
1860 def worker():
1861 try:
1862 lock.acquire()
1863 for d in diff_iter:
1864 lock.release()
1865 start = time.time()
1866 d.ComputePatch()
1867 dur = time.time() - start
1868 lock.acquire()
1869
1870 tf, sf, patch = d.GetPatch()
1871 if sf.name == tf.name:
1872 name = tf.name
1873 else:
1874 name = "%s (%s)" % (tf.name, sf.name)
1875 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001876 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001877 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001878 logger.info(
1879 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1880 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001881 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001882 except Exception:
1883 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001884 raise
1885
1886 # start worker threads; wait for them all to finish.
1887 threads = [threading.Thread(target=worker)
1888 for i in range(OPTIONS.worker_threads)]
1889 for th in threads:
1890 th.start()
1891 while threads:
1892 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001893
1894
Dan Albert8b72aef2015-03-23 19:13:21 -07001895class BlockDifference(object):
1896 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001897 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001898 self.tgt = tgt
1899 self.src = src
1900 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001901 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001902 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001903
Tao Baodd2a5892015-03-12 12:32:37 -07001904 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001905 version = max(
1906 int(i) for i in
1907 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001908 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001909 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001910
1911 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001912 version=self.version,
1913 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001914 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001915 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001916 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001917 self.touched_src_ranges = b.touched_src_ranges
1918 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001919
Yifan Hong10c530d2018-12-27 17:34:18 -08001920 # On devices with dynamic partitions, for new partitions,
1921 # src is None but OPTIONS.source_info_dict is not.
1922 if OPTIONS.source_info_dict is None:
1923 is_dynamic_build = OPTIONS.info_dict.get(
1924 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001925 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001926 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001927 is_dynamic_build = OPTIONS.source_info_dict.get(
1928 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001929 is_dynamic_source = partition in shlex.split(
1930 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001931
Yifan Hongbb2658d2019-01-25 12:30:58 -08001932 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001933 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1934
Yifan Hongbb2658d2019-01-25 12:30:58 -08001935 # For dynamic partitions builds, check partition list in both source
1936 # and target build because new partitions may be added, and existing
1937 # partitions may be removed.
1938 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1939
Yifan Hong10c530d2018-12-27 17:34:18 -08001940 if is_dynamic:
1941 self.device = 'map_partition("%s")' % partition
1942 else:
1943 if OPTIONS.source_info_dict is None:
1944 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1945 else:
1946 _, device_path = GetTypeAndDevice("/" + partition,
1947 OPTIONS.source_info_dict)
1948 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001949
Tao Baod8d14be2016-02-04 14:26:02 -08001950 @property
1951 def required_cache(self):
1952 return self._required_cache
1953
Tao Bao76def242017-11-21 09:25:31 -08001954 def WriteScript(self, script, output_zip, progress=None,
1955 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001956 if not self.src:
1957 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001958 script.Print("Patching %s image unconditionally..." % (self.partition,))
1959 else:
1960 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001961
Dan Albert8b72aef2015-03-23 19:13:21 -07001962 if progress:
1963 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001964 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001965
1966 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001967 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001968
Tao Bao9bc6bb22015-11-09 16:58:28 -08001969 def WriteStrictVerifyScript(self, script):
1970 """Verify all the blocks in the care_map, including clobbered blocks.
1971
1972 This differs from the WriteVerifyScript() function: a) it prints different
1973 error messages; b) it doesn't allow half-way updated images to pass the
1974 verification."""
1975
1976 partition = self.partition
1977 script.Print("Verifying %s..." % (partition,))
1978 ranges = self.tgt.care_map
1979 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001980 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001981 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1982 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001983 self.device, ranges_str,
1984 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001985 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001986 script.AppendExtra("")
1987
Tao Baod522bdc2016-04-12 15:53:16 -07001988 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001989 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001990
1991 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001992 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001993 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001994
1995 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001996 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001997 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001998 ranges = self.touched_src_ranges
1999 expected_sha1 = self.touched_src_sha1
2000 else:
2001 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2002 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002003
2004 # No blocks to be checked, skipping.
2005 if not ranges:
2006 return
2007
Tao Bao5ece99d2015-05-12 11:42:31 -07002008 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002009 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002010 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002011 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2012 '"%s.patch.dat")) then' % (
2013 self.device, ranges_str, expected_sha1,
2014 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002015 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002016 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002017
Tianjie Xufc3422a2015-12-15 11:53:59 -08002018 if self.version >= 4:
2019
2020 # Bug: 21124327
2021 # When generating incrementals for the system and vendor partitions in
2022 # version 4 or newer, explicitly check the first block (which contains
2023 # the superblock) of the partition to see if it's what we expect. If
2024 # this check fails, give an explicit log message about the partition
2025 # having been remounted R/W (the most likely explanation).
2026 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002027 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002028
2029 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002030 if partition == "system":
2031 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2032 else:
2033 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002034 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002035 'ifelse (block_image_recover({device}, "{ranges}") && '
2036 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002037 'package_extract_file("{partition}.transfer.list"), '
2038 '"{partition}.new.dat", "{partition}.patch.dat"), '
2039 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002040 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002041 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002042 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002043
Tao Baodd2a5892015-03-12 12:32:37 -07002044 # Abort the OTA update. Note that the incremental OTA cannot be applied
2045 # even if it may match the checksum of the target partition.
2046 # a) If version < 3, operations like move and erase will make changes
2047 # unconditionally and damage the partition.
2048 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002049 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002050 if partition == "system":
2051 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2052 else:
2053 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2054 script.AppendExtra((
2055 'abort("E%d: %s partition has unexpected contents");\n'
2056 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002057
Yifan Hong10c530d2018-12-27 17:34:18 -08002058 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002059 partition = self.partition
2060 script.Print('Verifying the updated %s image...' % (partition,))
2061 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2062 ranges = self.tgt.care_map
2063 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002064 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002065 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002066 self.device, ranges_str,
2067 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002068
2069 # Bug: 20881595
2070 # Verify that extended blocks are really zeroed out.
2071 if self.tgt.extended:
2072 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002073 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002074 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002075 self.device, ranges_str,
2076 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002077 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002078 if partition == "system":
2079 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2080 else:
2081 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002082 script.AppendExtra(
2083 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002084 ' abort("E%d: %s partition has unexpected non-zero contents after '
2085 'OTA update");\n'
2086 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002087 else:
2088 script.Print('Verified the updated %s image.' % (partition,))
2089
Tianjie Xu209db462016-05-24 17:34:52 -07002090 if partition == "system":
2091 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2092 else:
2093 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2094
Tao Bao5fcaaef2015-06-01 13:40:49 -07002095 script.AppendExtra(
2096 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002097 ' abort("E%d: %s partition has unexpected contents after OTA '
2098 'update");\n'
2099 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002100
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002101 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002102 ZipWrite(output_zip,
2103 '{}.transfer.list'.format(self.path),
2104 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002105
Tao Bao76def242017-11-21 09:25:31 -08002106 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2107 # its size. Quailty 9 almost triples the compression time but doesn't
2108 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002109 # zip | brotli(quality 6) | brotli(quality 9)
2110 # compressed_size: 942M | 869M (~8% reduced) | 854M
2111 # compression_time: 75s | 265s | 719s
2112 # decompression_time: 15s | 25s | 25s
2113
2114 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002115 brotli_cmd = ['brotli', '--quality=6',
2116 '--output={}.new.dat.br'.format(self.path),
2117 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002118 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002119 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002120
2121 new_data_name = '{}.new.dat.br'.format(self.partition)
2122 ZipWrite(output_zip,
2123 '{}.new.dat.br'.format(self.path),
2124 new_data_name,
2125 compress_type=zipfile.ZIP_STORED)
2126 else:
2127 new_data_name = '{}.new.dat'.format(self.partition)
2128 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2129
Dan Albert8e0178d2015-01-27 15:53:15 -08002130 ZipWrite(output_zip,
2131 '{}.patch.dat'.format(self.path),
2132 '{}.patch.dat'.format(self.partition),
2133 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002134
Tianjie Xu209db462016-05-24 17:34:52 -07002135 if self.partition == "system":
2136 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2137 else:
2138 code = ErrorCode.VENDOR_UPDATE_FAILURE
2139
Yifan Hong10c530d2018-12-27 17:34:18 -08002140 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002141 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002142 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002143 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002144 device=self.device, partition=self.partition,
2145 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002146 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002147
Dan Albert8b72aef2015-03-23 19:13:21 -07002148 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002149 data = source.ReadRangeSet(ranges)
2150 ctx = sha1()
2151
2152 for p in data:
2153 ctx.update(p)
2154
2155 return ctx.hexdigest()
2156
Tao Baoe9b61912015-07-09 17:37:49 -07002157 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2158 """Return the hash value for all zero blocks."""
2159 zero_block = '\x00' * 4096
2160 ctx = sha1()
2161 for _ in range(num_blocks):
2162 ctx.update(zero_block)
2163
2164 return ctx.hexdigest()
2165
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002166
2167DataImage = blockimgdiff.DataImage
Yifan Hong8a66a712019-04-04 15:37:57 -07002168EmptyImage = blockimgdiff.EmptyImage
Tao Bao76def242017-11-21 09:25:31 -08002169
Doug Zongker96a57e72010-09-26 14:57:41 -07002170# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002171PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002172 "ext4": "EMMC",
2173 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002174 "f2fs": "EMMC",
2175 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002176}
Doug Zongker96a57e72010-09-26 14:57:41 -07002177
Tao Bao76def242017-11-21 09:25:31 -08002178
Doug Zongker96a57e72010-09-26 14:57:41 -07002179def GetTypeAndDevice(mount_point, info):
2180 fstab = info["fstab"]
2181 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002182 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2183 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002184 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002185 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002186
2187
2188def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002189 """Parses and converts a PEM-encoded certificate into DER-encoded.
2190
2191 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2192
2193 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002194 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002195 """
2196 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002197 save = False
2198 for line in data.split("\n"):
2199 if "--END CERTIFICATE--" in line:
2200 break
2201 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002202 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002203 if "--BEGIN CERTIFICATE--" in line:
2204 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002205 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002206 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002207
Tao Bao04e1f012018-02-04 12:13:35 -08002208
2209def ExtractPublicKey(cert):
2210 """Extracts the public key (PEM-encoded) from the given certificate file.
2211
2212 Args:
2213 cert: The certificate filename.
2214
2215 Returns:
2216 The public key string.
2217
2218 Raises:
2219 AssertionError: On non-zero return from 'openssl'.
2220 """
2221 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2222 # While openssl 1.1 writes the key into the given filename followed by '-out',
2223 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2224 # stdout instead.
2225 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2226 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2227 pubkey, stderrdata = proc.communicate()
2228 assert proc.returncode == 0, \
2229 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2230 return pubkey
2231
2232
Tao Bao1ac886e2019-06-26 11:58:22 -07002233def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002234 """Extracts the AVB public key from the given public or private key.
2235
2236 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002237 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002238 key: The input key file, which should be PEM-encoded public or private key.
2239
2240 Returns:
2241 The path to the extracted AVB public key file.
2242 """
2243 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2244 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002245 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002246 return output
2247
2248
Doug Zongker412c02f2014-02-13 10:58:24 -08002249def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2250 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002251 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002252
Tao Bao6d5d6232018-03-09 17:04:42 -08002253 Most of the space in the boot and recovery images is just the kernel, which is
2254 identical for the two, so the resulting patch should be efficient. Add it to
2255 the output zip, along with a shell script that is run from init.rc on first
2256 boot to actually do the patching and install the new recovery image.
2257
2258 Args:
2259 input_dir: The top-level input directory of the target-files.zip.
2260 output_sink: The callback function that writes the result.
2261 recovery_img: File object for the recovery image.
2262 boot_img: File objects for the boot image.
2263 info_dict: A dict returned by common.LoadInfoDict() on the input
2264 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002265 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002266 if info_dict is None:
2267 info_dict = OPTIONS.info_dict
2268
Tao Bao6d5d6232018-03-09 17:04:42 -08002269 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002270
Tao Baof2cffbd2015-07-22 12:33:18 -07002271 if full_recovery_image:
2272 output_sink("etc/recovery.img", recovery_img.data)
2273
2274 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002275 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002276 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002277 # With system-root-image, boot and recovery images will have mismatching
2278 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2279 # to handle such a case.
2280 if system_root_image:
2281 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002282 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002283 assert not os.path.exists(path)
2284 else:
2285 diff_program = ["imgdiff"]
2286 if os.path.exists(path):
2287 diff_program.append("-b")
2288 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002289 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002290 else:
2291 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002292
2293 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2294 _, _, patch = d.ComputePatch()
2295 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002296
Dan Albertebb19aa2015-03-27 19:11:53 -07002297 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002298 # The following GetTypeAndDevice()s need to use the path in the target
2299 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002300 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2301 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2302 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002303 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002304
Tao Baof2cffbd2015-07-22 12:33:18 -07002305 if full_recovery_image:
2306 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002307if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2308 applypatch \\
2309 --flash /system/etc/recovery.img \\
2310 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2311 log -t recovery "Installing new recovery image: succeeded" || \\
2312 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002313else
2314 log -t recovery "Recovery image already installed"
2315fi
2316""" % {'type': recovery_type,
2317 'device': recovery_device,
2318 'sha1': recovery_img.sha1,
2319 'size': recovery_img.size}
2320 else:
2321 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002322if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2323 applypatch %(bonus_args)s \\
2324 --patch /system/recovery-from-boot.p \\
2325 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2326 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2327 log -t recovery "Installing new recovery image: succeeded" || \\
2328 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002329else
2330 log -t recovery "Recovery image already installed"
2331fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002332""" % {'boot_size': boot_img.size,
2333 'boot_sha1': boot_img.sha1,
2334 'recovery_size': recovery_img.size,
2335 'recovery_sha1': recovery_img.sha1,
2336 'boot_type': boot_type,
2337 'boot_device': boot_device,
2338 'recovery_type': recovery_type,
2339 'recovery_device': recovery_device,
2340 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002341
2342 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002343 # in the L release.
2344 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002345
Tao Bao32fcdab2018-10-12 10:30:39 -07002346 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002347
Tao Baoda30cfa2017-12-01 16:19:46 -08002348 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002349
2350
2351class DynamicPartitionUpdate(object):
2352 def __init__(self, src_group=None, tgt_group=None, progress=None,
2353 block_difference=None):
2354 self.src_group = src_group
2355 self.tgt_group = tgt_group
2356 self.progress = progress
2357 self.block_difference = block_difference
2358
2359 @property
2360 def src_size(self):
2361 if not self.block_difference:
2362 return 0
2363 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2364
2365 @property
2366 def tgt_size(self):
2367 if not self.block_difference:
2368 return 0
2369 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2370
2371 @staticmethod
2372 def _GetSparseImageSize(img):
2373 if not img:
2374 return 0
2375 return img.blocksize * img.total_blocks
2376
2377
2378class DynamicGroupUpdate(object):
2379 def __init__(self, src_size=None, tgt_size=None):
2380 # None: group does not exist. 0: no size limits.
2381 self.src_size = src_size
2382 self.tgt_size = tgt_size
2383
2384
2385class DynamicPartitionsDifference(object):
2386 def __init__(self, info_dict, block_diffs, progress_dict=None,
2387 source_info_dict=None):
2388 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002389 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002390
2391 self._remove_all_before_apply = False
2392 if source_info_dict is None:
2393 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002394 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002395
Tao Baof1113e92019-06-18 12:10:14 -07002396 block_diff_dict = collections.OrderedDict(
2397 [(e.partition, e) for e in block_diffs])
2398
Yifan Hong10c530d2018-12-27 17:34:18 -08002399 assert len(block_diff_dict) == len(block_diffs), \
2400 "Duplicated BlockDifference object for {}".format(
2401 [partition for partition, count in
2402 collections.Counter(e.partition for e in block_diffs).items()
2403 if count > 1])
2404
Yifan Hong79997e52019-01-23 16:56:19 -08002405 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002406
2407 for p, block_diff in block_diff_dict.items():
2408 self._partition_updates[p] = DynamicPartitionUpdate()
2409 self._partition_updates[p].block_difference = block_diff
2410
2411 for p, progress in progress_dict.items():
2412 if p in self._partition_updates:
2413 self._partition_updates[p].progress = progress
2414
2415 tgt_groups = shlex.split(info_dict.get(
2416 "super_partition_groups", "").strip())
2417 src_groups = shlex.split(source_info_dict.get(
2418 "super_partition_groups", "").strip())
2419
2420 for g in tgt_groups:
2421 for p in shlex.split(info_dict.get(
2422 "super_%s_partition_list" % g, "").strip()):
2423 assert p in self._partition_updates, \
2424 "{} is in target super_{}_partition_list but no BlockDifference " \
2425 "object is provided.".format(p, g)
2426 self._partition_updates[p].tgt_group = g
2427
2428 for g in src_groups:
2429 for p in shlex.split(source_info_dict.get(
2430 "super_%s_partition_list" % g, "").strip()):
2431 assert p in self._partition_updates, \
2432 "{} is in source super_{}_partition_list but no BlockDifference " \
2433 "object is provided.".format(p, g)
2434 self._partition_updates[p].src_group = g
2435
Yifan Hong45433e42019-01-18 13:55:25 -08002436 target_dynamic_partitions = set(shlex.split(info_dict.get(
2437 "dynamic_partition_list", "").strip()))
2438 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2439 if u.tgt_size)
2440 assert block_diffs_with_target == target_dynamic_partitions, \
2441 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2442 list(target_dynamic_partitions), list(block_diffs_with_target))
2443
2444 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2445 "dynamic_partition_list", "").strip()))
2446 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2447 if u.src_size)
2448 assert block_diffs_with_source == source_dynamic_partitions, \
2449 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2450 list(source_dynamic_partitions), list(block_diffs_with_source))
2451
Yifan Hong10c530d2018-12-27 17:34:18 -08002452 if self._partition_updates:
2453 logger.info("Updating dynamic partitions %s",
2454 self._partition_updates.keys())
2455
Yifan Hong79997e52019-01-23 16:56:19 -08002456 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002457
2458 for g in tgt_groups:
2459 self._group_updates[g] = DynamicGroupUpdate()
2460 self._group_updates[g].tgt_size = int(info_dict.get(
2461 "super_%s_group_size" % g, "0").strip())
2462
2463 for g in src_groups:
2464 if g not in self._group_updates:
2465 self._group_updates[g] = DynamicGroupUpdate()
2466 self._group_updates[g].src_size = int(source_info_dict.get(
2467 "super_%s_group_size" % g, "0").strip())
2468
2469 self._Compute()
2470
2471 def WriteScript(self, script, output_zip, write_verify_script=False):
2472 script.Comment('--- Start patching dynamic partitions ---')
2473 for p, u in self._partition_updates.items():
2474 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2475 script.Comment('Patch partition %s' % p)
2476 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2477 write_verify_script=False)
2478
2479 op_list_path = MakeTempFile()
2480 with open(op_list_path, 'w') as f:
2481 for line in self._op_list:
2482 f.write('{}\n'.format(line))
2483
2484 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2485
2486 script.Comment('Update dynamic partition metadata')
2487 script.AppendExtra('assert(update_dynamic_partitions('
2488 'package_extract_file("dynamic_partitions_op_list")));')
2489
2490 if write_verify_script:
2491 for p, u in self._partition_updates.items():
2492 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2493 u.block_difference.WritePostInstallVerifyScript(script)
2494 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2495
2496 for p, u in self._partition_updates.items():
2497 if u.tgt_size and u.src_size <= u.tgt_size:
2498 script.Comment('Patch partition %s' % p)
2499 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2500 write_verify_script=write_verify_script)
2501 if write_verify_script:
2502 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2503
2504 script.Comment('--- End patching dynamic partitions ---')
2505
2506 def _Compute(self):
2507 self._op_list = list()
2508
2509 def append(line):
2510 self._op_list.append(line)
2511
2512 def comment(line):
2513 self._op_list.append("# %s" % line)
2514
2515 if self._remove_all_before_apply:
2516 comment('Remove all existing dynamic partitions and groups before '
2517 'applying full OTA')
2518 append('remove_all_groups')
2519
2520 for p, u in self._partition_updates.items():
2521 if u.src_group and not u.tgt_group:
2522 append('remove %s' % p)
2523
2524 for p, u in self._partition_updates.items():
2525 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2526 comment('Move partition %s from %s to default' % (p, u.src_group))
2527 append('move %s default' % p)
2528
2529 for p, u in self._partition_updates.items():
2530 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2531 comment('Shrink partition %s from %d to %d' %
2532 (p, u.src_size, u.tgt_size))
2533 append('resize %s %s' % (p, u.tgt_size))
2534
2535 for g, u in self._group_updates.items():
2536 if u.src_size is not None and u.tgt_size is None:
2537 append('remove_group %s' % g)
2538 if (u.src_size is not None and u.tgt_size is not None and
2539 u.src_size > u.tgt_size):
2540 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2541 append('resize_group %s %d' % (g, u.tgt_size))
2542
2543 for g, u in self._group_updates.items():
2544 if u.src_size is None and u.tgt_size is not None:
2545 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2546 append('add_group %s %d' % (g, u.tgt_size))
2547 if (u.src_size is not None and u.tgt_size is not None and
2548 u.src_size < u.tgt_size):
2549 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2550 append('resize_group %s %d' % (g, u.tgt_size))
2551
2552 for p, u in self._partition_updates.items():
2553 if u.tgt_group and not u.src_group:
2554 comment('Add partition %s to group %s' % (p, u.tgt_group))
2555 append('add %s %s' % (p, u.tgt_group))
2556
2557 for p, u in self._partition_updates.items():
2558 if u.tgt_size and u.src_size < u.tgt_size:
2559 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2560 append('resize %s %d' % (p, u.tgt_size))
2561
2562 for p, u in self._partition_updates.items():
2563 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2564 comment('Move partition %s from default to %s' %
2565 (p, u.tgt_group))
2566 append('move %s %s' % (p, u.tgt_group))