blob: 4bebef50b6c3597fed4a412ff53cc6f8d9944880 [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
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900462def LoadDictionaryFromFile(file_path):
463 with open(file_path) as f:
464 lines = list(f.read().splitlines())
465
466 return LoadDictionaryFromLines(lines)
467
468
Michael Runge6e836112014-04-15 17:40:21 -0700469def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700470 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700471 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700472 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700473 if not line or line.startswith("#"):
474 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700475 if "=" in line:
476 name, value = line.split("=", 1)
477 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700478 return d
479
Tao Baod1de6f32017-03-01 16:38:48 -0800480
Tianjie Xucfa86222016-03-07 16:31:19 -0800481def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
482 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700483 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800484 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700485 self.mount_point = mount_point
486 self.fs_type = fs_type
487 self.device = device
488 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700489 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700490
491 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800492 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700493 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700494 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700495 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700496
Tao Baod1de6f32017-03-01 16:38:48 -0800497 assert fstab_version == 2
498
499 d = {}
500 for line in data.split("\n"):
501 line = line.strip()
502 if not line or line.startswith("#"):
503 continue
504
505 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
506 pieces = line.split()
507 if len(pieces) != 5:
508 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
509
510 # Ignore entries that are managed by vold.
511 options = pieces[4]
512 if "voldmanaged=" in options:
513 continue
514
515 # It's a good line, parse it.
516 length = 0
517 options = options.split(",")
518 for i in options:
519 if i.startswith("length="):
520 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800521 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800522 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700523 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800524
Tao Baod1de6f32017-03-01 16:38:48 -0800525 mount_flags = pieces[3]
526 # Honor the SELinux context if present.
527 context = None
528 for i in mount_flags.split(","):
529 if i.startswith("context="):
530 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800531
Tao Baod1de6f32017-03-01 16:38:48 -0800532 mount_point = pieces[1]
533 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
534 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800535
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700536 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700537 # system. Other areas assume system is always at "/system" so point /system
538 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700539 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800540 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700541 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700542 return d
543
544
Doug Zongker37974732010-09-16 17:44:38 -0700545def DumpInfoDict(d):
546 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700547 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700548
Dan Albert8b72aef2015-03-23 19:13:21 -0700549
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800550def AppendAVBSigningArgs(cmd, partition):
551 """Append signing arguments for avbtool."""
552 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
553 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
554 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
555 if key_path and algorithm:
556 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700557 avb_salt = OPTIONS.info_dict.get("avb_salt")
558 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700559 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700560 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800561
562
Tao Bao02a08592018-07-22 12:40:45 -0700563def GetAvbChainedPartitionArg(partition, info_dict, key=None):
564 """Constructs and returns the arg to build or verify a chained partition.
565
566 Args:
567 partition: The partition name.
568 info_dict: The info dict to look up the key info and rollback index
569 location.
570 key: The key to be used for building or verifying the partition. Defaults to
571 the key listed in info_dict.
572
573 Returns:
574 A string of form "partition:rollback_index_location:key" that can be used to
575 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700576 """
577 if key is None:
578 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao2cc0ca12019-03-15 10:44:43 -0700579 pubkey_path = ExtractAvbPublicKey(key)
Tao Bao02a08592018-07-22 12:40:45 -0700580 rollback_index_location = info_dict[
581 "avb_" + partition + "_rollback_index_location"]
582 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
583
584
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700585def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800586 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700587 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700588
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700589 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800590 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
591 we are building a two-step special image (i.e. building a recovery image to
592 be loaded into /boot in two-step OTAs).
593
594 Return the image data, or None if sourcedir does not appear to contains files
595 for building the requested image.
596 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700597
598 def make_ramdisk():
599 ramdisk_img = tempfile.NamedTemporaryFile()
600
601 if os.access(fs_config_file, os.F_OK):
602 cmd = ["mkbootfs", "-f", fs_config_file,
603 os.path.join(sourcedir, "RAMDISK")]
604 else:
605 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
606 p1 = Run(cmd, stdout=subprocess.PIPE)
607 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
608
609 p2.wait()
610 p1.wait()
611 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
612 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
613
614 return ramdisk_img
615
616 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
617 return None
618
619 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700620 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700621
Doug Zongkerd5131602012-08-02 14:46:42 -0700622 if info_dict is None:
623 info_dict = OPTIONS.info_dict
624
Doug Zongkereef39442009-04-02 12:14:19 -0700625 img = tempfile.NamedTemporaryFile()
626
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700627 if has_ramdisk:
628 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700629
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800630 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
631 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
632
633 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700634
Benoit Fradina45a8682014-07-14 21:00:43 +0200635 fn = os.path.join(sourcedir, "second")
636 if os.access(fn, os.F_OK):
637 cmd.append("--second")
638 cmd.append(fn)
639
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800640 fn = os.path.join(sourcedir, "dtb")
641 if os.access(fn, os.F_OK):
642 cmd.append("--dtb")
643 cmd.append(fn)
644
Doug Zongker171f1cd2009-06-15 22:36:37 -0700645 fn = os.path.join(sourcedir, "cmdline")
646 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700647 cmd.append("--cmdline")
648 cmd.append(open(fn).read().rstrip("\n"))
649
650 fn = os.path.join(sourcedir, "base")
651 if os.access(fn, os.F_OK):
652 cmd.append("--base")
653 cmd.append(open(fn).read().rstrip("\n"))
654
Ying Wang4de6b5b2010-08-25 14:29:34 -0700655 fn = os.path.join(sourcedir, "pagesize")
656 if os.access(fn, os.F_OK):
657 cmd.append("--pagesize")
658 cmd.append(open(fn).read().rstrip("\n"))
659
Tao Bao76def242017-11-21 09:25:31 -0800660 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700661 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700662 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700663
Tao Bao76def242017-11-21 09:25:31 -0800664 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000665 if args and args.strip():
666 cmd.extend(shlex.split(args))
667
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700668 if has_ramdisk:
669 cmd.extend(["--ramdisk", ramdisk_img.name])
670
Tao Baod95e9fd2015-03-29 23:07:41 -0700671 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800672 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700673 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700674 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700675 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700676 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700677
Tao Baobf70c312017-07-11 17:27:55 -0700678 # "boot" or "recovery", without extension.
679 partition_name = os.path.basename(sourcedir).lower()
680
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800681 if partition_name == "recovery":
682 if info_dict.get("include_recovery_dtbo") == "true":
683 fn = os.path.join(sourcedir, "recovery_dtbo")
684 cmd.extend(["--recovery_dtbo", fn])
685 if info_dict.get("include_recovery_acpio") == "true":
686 fn = os.path.join(sourcedir, "recovery_acpio")
687 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700688
Tao Bao986ee862018-10-04 15:46:16 -0700689 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700690
Tao Bao76def242017-11-21 09:25:31 -0800691 if (info_dict.get("boot_signer") == "true" and
692 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800693 # Hard-code the path as "/boot" for two-step special recovery image (which
694 # will be loaded into /boot during the two-step OTA).
695 if two_step_image:
696 path = "/boot"
697 else:
Tao Baobf70c312017-07-11 17:27:55 -0700698 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700699 cmd = [OPTIONS.boot_signer_path]
700 cmd.extend(OPTIONS.boot_signer_args)
701 cmd.extend([path, img.name,
702 info_dict["verity_key"] + ".pk8",
703 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700704 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700705
Tao Baod95e9fd2015-03-29 23:07:41 -0700706 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800707 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700708 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700709 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800710 # We have switched from the prebuilt futility binary to using the tool
711 # (futility-host) built from the source. Override the setting in the old
712 # TF.zip.
713 futility = info_dict["futility"]
714 if futility.startswith("prebuilts/"):
715 futility = "futility-host"
716 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700717 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700718 info_dict["vboot_key"] + ".vbprivk",
719 info_dict["vboot_subkey"] + ".vbprivk",
720 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700721 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700722 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700723
Tao Baof3282b42015-04-01 11:21:55 -0700724 # Clean up the temp files.
725 img_unsigned.close()
726 img_keyblock.close()
727
David Zeuthen8fecb282017-12-01 16:24:01 -0500728 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800729 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700730 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500731 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400732 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700733 "--partition_size", str(part_size), "--partition_name",
734 partition_name]
735 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500736 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400737 if args and args.strip():
738 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700739 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500740
741 img.seek(os.SEEK_SET, 0)
742 data = img.read()
743
744 if has_ramdisk:
745 ramdisk_img.close()
746 img.close()
747
748 return data
749
750
Doug Zongkerd5131602012-08-02 14:46:42 -0700751def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800752 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700753 """Return a File object with the desired bootable image.
754
755 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
756 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
757 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700758
Doug Zongker55d93282011-01-25 17:03:34 -0800759 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
760 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700761 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800762 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700763
764 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
765 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700766 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700767 return File.FromLocalFile(name, prebuilt_path)
768
Tao Bao32fcdab2018-10-12 10:30:39 -0700769 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700770
771 if info_dict is None:
772 info_dict = OPTIONS.info_dict
773
774 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800775 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
776 # for recovery.
777 has_ramdisk = (info_dict.get("system_root_image") != "true" or
778 prebuilt_name != "boot.img" or
779 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700780
Doug Zongker6f1d0312014-08-22 08:07:12 -0700781 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400782 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
783 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800784 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700785 if data:
786 return File(name, data)
787 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800788
Doug Zongkereef39442009-04-02 12:14:19 -0700789
Narayan Kamatha07bf042017-08-14 14:49:21 +0100790def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800791 """Gunzips the given gzip compressed file to a given output file."""
792 with gzip.open(in_filename, "rb") as in_file, \
793 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100794 shutil.copyfileobj(in_file, out_file)
795
796
Tao Bao0ff15de2019-03-20 11:26:06 -0700797def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800798 """Unzips the archive to the given directory.
799
800 Args:
801 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800802 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700803 patterns: Files to unzip from the archive. If omitted, will unzip the entire
804 archvie. Non-matching patterns will be filtered out. If there's no match
805 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800806 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800807 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700808 if patterns is not None:
809 # Filter out non-matching patterns. unzip will complain otherwise.
810 with zipfile.ZipFile(filename) as input_zip:
811 names = input_zip.namelist()
812 filtered = [
813 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
814
815 # There isn't any matching files. Don't unzip anything.
816 if not filtered:
817 return
818 cmd.extend(filtered)
819
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800820 RunAndCheckOutput(cmd)
821
822
Doug Zongker75f17362009-12-08 13:46:44 -0800823def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800824 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800825
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800826 Args:
827 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
828 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
829
830 pattern: Files to unzip from the archive. If omitted, will unzip the entire
831 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800832
Tao Bao1c830bf2017-12-25 10:43:47 -0800833 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800834 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800835 """
Doug Zongkereef39442009-04-02 12:14:19 -0700836
Tao Bao1c830bf2017-12-25 10:43:47 -0800837 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800838 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
839 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800840 UnzipToDir(m.group(1), tmp, pattern)
841 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800842 filename = m.group(1)
843 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800844 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800845
Tao Baodba59ee2018-01-09 13:21:02 -0800846 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700847
848
Yifan Hong8a66a712019-04-04 15:37:57 -0700849def GetUserImage(which, tmpdir, input_zip,
850 info_dict=None,
851 allow_shared_blocks=None,
852 hashtree_info_generator=None,
853 reset_file_map=False):
854 """Returns an Image object suitable for passing to BlockImageDiff.
855
856 This function loads the specified image from the given path. If the specified
857 image is sparse, it also performs additional processing for OTA purpose. For
858 example, it always adds block 0 to clobbered blocks list. It also detects
859 files that cannot be reconstructed from the block list, for whom we should
860 avoid applying imgdiff.
861
862 Args:
863 which: The partition name.
864 tmpdir: The directory that contains the prebuilt image and block map file.
865 input_zip: The target-files ZIP archive.
866 info_dict: The dict to be looked up for relevant info.
867 allow_shared_blocks: If image is sparse, whether having shared blocks is
868 allowed. If none, it is looked up from info_dict.
869 hashtree_info_generator: If present and image is sparse, generates the
870 hashtree_info for this sparse image.
871 reset_file_map: If true and image is sparse, reset file map before returning
872 the image.
873 Returns:
874 A Image object. If it is a sparse image and reset_file_map is False, the
875 image will have file_map info loaded.
876 """
Tao Baoc1a1ec32019-06-18 16:29:37 -0700877 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -0700878 info_dict = LoadInfoDict(input_zip)
879
880 is_sparse = info_dict.get("extfs_sparse_flag")
881
882 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
883 # shared blocks (i.e. some blocks will show up in multiple files' block
884 # list). We can only allocate such shared blocks to the first "owner", and
885 # disable imgdiff for all later occurrences.
886 if allow_shared_blocks is None:
887 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
888
889 if is_sparse:
890 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
891 hashtree_info_generator)
892 if reset_file_map:
893 img.ResetFileMap()
894 return img
895 else:
896 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
897
898
899def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
900 """Returns a Image object suitable for passing to BlockImageDiff.
901
902 This function loads the specified non-sparse image from the given path.
903
904 Args:
905 which: The partition name.
906 tmpdir: The directory that contains the prebuilt image and block map file.
907 Returns:
908 A Image object.
909 """
910 path = os.path.join(tmpdir, "IMAGES", which + ".img")
911 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
912
913 # The image and map files must have been created prior to calling
914 # ota_from_target_files.py (since LMP).
915 assert os.path.exists(path) and os.path.exists(mappath)
916
917 return blockimgdiff.FileImage(path, hashtree_info_generator=
918 hashtree_info_generator)
919
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700920def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
921 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800922 """Returns a SparseImage object suitable for passing to BlockImageDiff.
923
924 This function loads the specified sparse image from the given path, and
925 performs additional processing for OTA purpose. For example, it always adds
926 block 0 to clobbered blocks list. It also detects files that cannot be
927 reconstructed from the block list, for whom we should avoid applying imgdiff.
928
929 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700930 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800931 tmpdir: The directory that contains the prebuilt image and block map file.
932 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800933 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700934 hashtree_info_generator: If present, generates the hashtree_info for this
935 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800936 Returns:
937 A SparseImage object, with file_map info loaded.
938 """
Tao Baoc765cca2018-01-31 17:32:40 -0800939 path = os.path.join(tmpdir, "IMAGES", which + ".img")
940 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
941
942 # The image and map files must have been created prior to calling
943 # ota_from_target_files.py (since LMP).
944 assert os.path.exists(path) and os.path.exists(mappath)
945
946 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
947 # it to clobbered_blocks so that it will be written to the target
948 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
949 clobbered_blocks = "0"
950
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700951 image = sparse_img.SparseImage(
952 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
953 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800954
955 # block.map may contain less blocks, because mke2fs may skip allocating blocks
956 # if they contain all zeros. We can't reconstruct such a file from its block
957 # list. Tag such entries accordingly. (Bug: 65213616)
958 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800959 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700960 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800961 continue
962
Tom Cherryd14b8952018-08-09 14:26:00 -0700963 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
964 # filename listed in system.map may contain an additional leading slash
965 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
966 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -0800967 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -0700968
Tom Cherryd14b8952018-08-09 14:26:00 -0700969 # Special handling another case, where files not under /system
970 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700971 if which == 'system' and not arcname.startswith('SYSTEM'):
972 arcname = 'ROOT/' + arcname
973
974 assert arcname in input_zip.namelist(), \
975 "Failed to find the ZIP entry for {}".format(entry)
976
Tao Baoc765cca2018-01-31 17:32:40 -0800977 info = input_zip.getinfo(arcname)
978 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800979
980 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800981 # image, check the original block list to determine its completeness. Note
982 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800983 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800984 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800985
Tao Baoc765cca2018-01-31 17:32:40 -0800986 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
987 ranges.extra['incomplete'] = True
988
989 return image
990
991
Doug Zongkereef39442009-04-02 12:14:19 -0700992def GetKeyPasswords(keylist):
993 """Given a list of keys, prompt the user to enter passwords for
994 those which require them. Return a {key: password} dict. password
995 will be None if the key has no password."""
996
Doug Zongker8ce7c252009-05-22 13:34:54 -0700997 no_passwords = []
998 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700999 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001000 devnull = open("/dev/null", "w+b")
1001 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001002 # We don't need a password for things that aren't really keys.
1003 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001004 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001005 continue
1006
T.R. Fullhart37e10522013-03-18 10:31:26 -07001007 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001008 "-inform", "DER", "-nocrypt"],
1009 stdin=devnull.fileno(),
1010 stdout=devnull.fileno(),
1011 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001012 p.communicate()
1013 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001014 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001015 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001016 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001017 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1018 "-inform", "DER", "-passin", "pass:"],
1019 stdin=devnull.fileno(),
1020 stdout=devnull.fileno(),
1021 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001022 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001023 if p.returncode == 0:
1024 # Encrypted key with empty string as password.
1025 key_passwords[k] = ''
1026 elif stderr.startswith('Error decrypting key'):
1027 # Definitely encrypted key.
1028 # It would have said "Error reading key" if it didn't parse correctly.
1029 need_passwords.append(k)
1030 else:
1031 # Potentially, a type of key that openssl doesn't understand.
1032 # We'll let the routines in signapk.jar handle it.
1033 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001034 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001035
T.R. Fullhart37e10522013-03-18 10:31:26 -07001036 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001037 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001038 return key_passwords
1039
1040
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001041def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001042 """Gets the minSdkVersion declared in the APK.
1043
1044 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1045 This can be both a decimal number (API Level) or a codename.
1046
1047 Args:
1048 apk_name: The APK filename.
1049
1050 Returns:
1051 The parsed SDK version string.
1052
1053 Raises:
1054 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001055 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001056 proc = Run(
1057 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1058 stderr=subprocess.PIPE)
1059 stdoutdata, stderrdata = proc.communicate()
1060 if proc.returncode != 0:
1061 raise ExternalError(
1062 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1063 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001064
Tao Baof47bf0f2018-03-21 23:28:51 -07001065 for line in stdoutdata.split("\n"):
1066 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001067 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1068 if m:
1069 return m.group(1)
1070 raise ExternalError("No minSdkVersion returned by aapt")
1071
1072
1073def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001074 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001075
Tao Baof47bf0f2018-03-21 23:28:51 -07001076 If minSdkVersion is set to a codename, it is translated to a number using the
1077 provided map.
1078
1079 Args:
1080 apk_name: The APK filename.
1081
1082 Returns:
1083 The parsed SDK version number.
1084
1085 Raises:
1086 ExternalError: On failing to get the min SDK version number.
1087 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001088 version = GetMinSdkVersion(apk_name)
1089 try:
1090 return int(version)
1091 except ValueError:
1092 # Not a decimal number. Codename?
1093 if version in codename_to_api_level_map:
1094 return codename_to_api_level_map[version]
1095 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001096 raise ExternalError(
1097 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1098 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001099
1100
1101def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001102 codename_to_api_level_map=None, whole_file=False,
1103 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001104 """Sign the input_name zip/jar/apk, producing output_name. Use the
1105 given key and password (the latter may be None if the key does not
1106 have a password.
1107
Doug Zongker951495f2009-08-14 12:44:19 -07001108 If whole_file is true, use the "-w" option to SignApk to embed a
1109 signature that covers the whole file in the archive comment of the
1110 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001111
1112 min_api_level is the API Level (int) of the oldest platform this file may end
1113 up on. If not specified for an APK, the API Level is obtained by interpreting
1114 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1115
1116 codename_to_api_level_map is needed to translate the codename which may be
1117 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001118
1119 Caller may optionally specify extra args to be passed to SignApk, which
1120 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001121 """
Tao Bao76def242017-11-21 09:25:31 -08001122 if codename_to_api_level_map is None:
1123 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001124 if extra_signapk_args is None:
1125 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001126
Alex Klyubin9667b182015-12-10 13:38:50 -08001127 java_library_path = os.path.join(
1128 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1129
Tao Baoe95540e2016-11-08 12:08:53 -08001130 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1131 ["-Djava.library.path=" + java_library_path,
1132 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001133 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001134 if whole_file:
1135 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001136
1137 min_sdk_version = min_api_level
1138 if min_sdk_version is None:
1139 if not whole_file:
1140 min_sdk_version = GetMinSdkVersionInt(
1141 input_name, codename_to_api_level_map)
1142 if min_sdk_version is not None:
1143 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1144
T.R. Fullhart37e10522013-03-18 10:31:26 -07001145 cmd.extend([key + OPTIONS.public_key_suffix,
1146 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001147 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001148
Tao Bao73dd4f42018-10-04 16:25:33 -07001149 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001150 if password is not None:
1151 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001152 stdoutdata, _ = proc.communicate(password)
1153 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001154 raise ExternalError(
1155 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001156 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001157
Doug Zongkereef39442009-04-02 12:14:19 -07001158
Doug Zongker37974732010-09-16 17:44:38 -07001159def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001160 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001161
Tao Bao9dd909e2017-11-14 11:27:32 -08001162 For non-AVB images, raise exception if the data is too big. Print a warning
1163 if the data is nearing the maximum size.
1164
1165 For AVB images, the actual image size should be identical to the limit.
1166
1167 Args:
1168 data: A string that contains all the data for the partition.
1169 target: The partition name. The ".img" suffix is optional.
1170 info_dict: The dict to be looked up for relevant info.
1171 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001172 if target.endswith(".img"):
1173 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001174 mount_point = "/" + target
1175
Ying Wangf8824af2014-06-03 14:07:27 -07001176 fs_type = None
1177 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001178 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001179 if mount_point == "/userdata":
1180 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001181 p = info_dict["fstab"][mount_point]
1182 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001183 device = p.device
1184 if "/" in device:
1185 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001186 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001187 if not fs_type or not limit:
1188 return
Doug Zongkereef39442009-04-02 12:14:19 -07001189
Andrew Boie0f9aec82012-02-14 09:32:52 -08001190 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001191 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1192 # path.
1193 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1194 if size != limit:
1195 raise ExternalError(
1196 "Mismatching image size for %s: expected %d actual %d" % (
1197 target, limit, size))
1198 else:
1199 pct = float(size) * 100.0 / limit
1200 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1201 if pct >= 99.0:
1202 raise ExternalError(msg)
1203 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001204 logger.warning("\n WARNING: %s\n", msg)
1205 else:
1206 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001207
1208
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001209def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001210 """Parses the APK certs info from a given target-files zip.
1211
1212 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1213 tuple with the following elements: (1) a dictionary that maps packages to
1214 certs (based on the "certificate" and "private_key" attributes in the file;
1215 (2) a string representing the extension of compressed APKs in the target files
1216 (e.g ".gz", ".bro").
1217
1218 Args:
1219 tf_zip: The input target_files ZipFile (already open).
1220
1221 Returns:
1222 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1223 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1224 no compressed APKs.
1225 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001226 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001227 compressed_extension = None
1228
Tao Bao0f990332017-09-08 19:02:54 -07001229 # META/apkcerts.txt contains the info for _all_ the packages known at build
1230 # time. Filter out the ones that are not installed.
1231 installed_files = set()
1232 for name in tf_zip.namelist():
1233 basename = os.path.basename(name)
1234 if basename:
1235 installed_files.add(basename)
1236
Tao Baoda30cfa2017-12-01 16:19:46 -08001237 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001238 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001239 if not line:
1240 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001241 m = re.match(
1242 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1243 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1244 line)
1245 if not m:
1246 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001247
Tao Bao818ddf52018-01-05 11:17:34 -08001248 matches = m.groupdict()
1249 cert = matches["CERT"]
1250 privkey = matches["PRIVKEY"]
1251 name = matches["NAME"]
1252 this_compressed_extension = matches["COMPRESSED"]
1253
1254 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1255 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1256 if cert in SPECIAL_CERT_STRINGS and not privkey:
1257 certmap[name] = cert
1258 elif (cert.endswith(OPTIONS.public_key_suffix) and
1259 privkey.endswith(OPTIONS.private_key_suffix) and
1260 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1261 certmap[name] = cert[:-public_key_suffix_len]
1262 else:
1263 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1264
1265 if not this_compressed_extension:
1266 continue
1267
1268 # Only count the installed files.
1269 filename = name + '.' + this_compressed_extension
1270 if filename not in installed_files:
1271 continue
1272
1273 # Make sure that all the values in the compression map have the same
1274 # extension. We don't support multiple compression methods in the same
1275 # system image.
1276 if compressed_extension:
1277 if this_compressed_extension != compressed_extension:
1278 raise ValueError(
1279 "Multiple compressed extensions: {} vs {}".format(
1280 compressed_extension, this_compressed_extension))
1281 else:
1282 compressed_extension = this_compressed_extension
1283
1284 return (certmap,
1285 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001286
1287
Doug Zongkereef39442009-04-02 12:14:19 -07001288COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001289Global options
1290
1291 -p (--path) <dir>
1292 Prepend <dir>/bin to the list of places to search for binaries run by this
1293 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001294
Doug Zongker05d3dea2009-06-22 11:32:31 -07001295 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001296 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001297
Tao Bao30df8b42018-04-23 15:32:53 -07001298 -x (--extra) <key=value>
1299 Add a key/value pair to the 'extras' dict, which device-specific extension
1300 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001301
Doug Zongkereef39442009-04-02 12:14:19 -07001302 -v (--verbose)
1303 Show command lines being executed.
1304
1305 -h (--help)
1306 Display this usage message and exit.
1307"""
1308
1309def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001310 print(docstring.rstrip("\n"))
1311 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001312
1313
1314def ParseOptions(argv,
1315 docstring,
1316 extra_opts="", extra_long_opts=(),
1317 extra_option_handler=None):
1318 """Parse the options in argv and return any arguments that aren't
1319 flags. docstring is the calling module's docstring, to be displayed
1320 for errors and -h. extra_opts and extra_long_opts are for flags
1321 defined by the caller, which are processed by passing them to
1322 extra_option_handler."""
1323
1324 try:
1325 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001326 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001327 ["help", "verbose", "path=", "signapk_path=",
1328 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001329 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001330 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1331 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001332 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001333 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001334 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001335 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001336 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001337 sys.exit(2)
1338
Doug Zongkereef39442009-04-02 12:14:19 -07001339 for o, a in opts:
1340 if o in ("-h", "--help"):
1341 Usage(docstring)
1342 sys.exit()
1343 elif o in ("-v", "--verbose"):
1344 OPTIONS.verbose = True
1345 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001346 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001347 elif o in ("--signapk_path",):
1348 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001349 elif o in ("--signapk_shared_library_path",):
1350 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001351 elif o in ("--extra_signapk_args",):
1352 OPTIONS.extra_signapk_args = shlex.split(a)
1353 elif o in ("--java_path",):
1354 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001355 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001356 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001357 elif o in ("--public_key_suffix",):
1358 OPTIONS.public_key_suffix = a
1359 elif o in ("--private_key_suffix",):
1360 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001361 elif o in ("--boot_signer_path",):
1362 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001363 elif o in ("--boot_signer_args",):
1364 OPTIONS.boot_signer_args = shlex.split(a)
1365 elif o in ("--verity_signer_path",):
1366 OPTIONS.verity_signer_path = a
1367 elif o in ("--verity_signer_args",):
1368 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001369 elif o in ("-s", "--device_specific"):
1370 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001371 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001372 key, value = a.split("=", 1)
1373 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001374 else:
1375 if extra_option_handler is None or not extra_option_handler(o, a):
1376 assert False, "unknown option \"%s\"" % (o,)
1377
Doug Zongker85448772014-09-09 14:59:20 -07001378 if OPTIONS.search_path:
1379 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1380 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001381
1382 return args
1383
1384
Tao Bao4c851b12016-09-19 13:54:38 -07001385def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001386 """Make a temp file and add it to the list of things to be deleted
1387 when Cleanup() is called. Return the filename."""
1388 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1389 os.close(fd)
1390 OPTIONS.tempfiles.append(fn)
1391 return fn
1392
1393
Tao Bao1c830bf2017-12-25 10:43:47 -08001394def MakeTempDir(prefix='tmp', suffix=''):
1395 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1396
1397 Returns:
1398 The absolute pathname of the new directory.
1399 """
1400 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1401 OPTIONS.tempfiles.append(dir_name)
1402 return dir_name
1403
1404
Doug Zongkereef39442009-04-02 12:14:19 -07001405def Cleanup():
1406 for i in OPTIONS.tempfiles:
1407 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001408 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001409 else:
1410 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001411 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001412
1413
1414class PasswordManager(object):
1415 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001416 self.editor = os.getenv("EDITOR")
1417 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001418
1419 def GetPasswords(self, items):
1420 """Get passwords corresponding to each string in 'items',
1421 returning a dict. (The dict may have keys in addition to the
1422 values in 'items'.)
1423
1424 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1425 user edit that file to add more needed passwords. If no editor is
1426 available, or $ANDROID_PW_FILE isn't define, prompts the user
1427 interactively in the ordinary way.
1428 """
1429
1430 current = self.ReadFile()
1431
1432 first = True
1433 while True:
1434 missing = []
1435 for i in items:
1436 if i not in current or not current[i]:
1437 missing.append(i)
1438 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001439 if not missing:
1440 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001441
1442 for i in missing:
1443 current[i] = ""
1444
1445 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001446 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001447 if sys.version_info[0] >= 3:
1448 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001449 answer = raw_input("try to edit again? [y]> ").strip()
1450 if answer and answer[0] not in 'yY':
1451 raise RuntimeError("key passwords unavailable")
1452 first = False
1453
1454 current = self.UpdateAndReadFile(current)
1455
Dan Albert8b72aef2015-03-23 19:13:21 -07001456 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001457 """Prompt the user to enter a value (password) for each key in
1458 'current' whose value is fales. Returns a new dict with all the
1459 values.
1460 """
1461 result = {}
1462 for k, v in sorted(current.iteritems()):
1463 if v:
1464 result[k] = v
1465 else:
1466 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001467 result[k] = getpass.getpass(
1468 "Enter password for %s key> " % k).strip()
1469 if result[k]:
1470 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001471 return result
1472
1473 def UpdateAndReadFile(self, current):
1474 if not self.editor or not self.pwfile:
1475 return self.PromptResult(current)
1476
1477 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001478 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001479 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1480 f.write("# (Additional spaces are harmless.)\n\n")
1481
1482 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001483 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1484 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001485 f.write("[[[ %s ]]] %s\n" % (v, k))
1486 if not v and first_line is None:
1487 # position cursor on first line with no password.
1488 first_line = i + 4
1489 f.close()
1490
Tao Bao986ee862018-10-04 15:46:16 -07001491 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001492
1493 return self.ReadFile()
1494
1495 def ReadFile(self):
1496 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001497 if self.pwfile is None:
1498 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001499 try:
1500 f = open(self.pwfile, "r")
1501 for line in f:
1502 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001503 if not line or line[0] == '#':
1504 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001505 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1506 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001507 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001508 else:
1509 result[m.group(2)] = m.group(1)
1510 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001511 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001512 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001513 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001514 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001515
1516
Dan Albert8e0178d2015-01-27 15:53:15 -08001517def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1518 compress_type=None):
1519 import datetime
1520
1521 # http://b/18015246
1522 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1523 # for files larger than 2GiB. We can work around this by adjusting their
1524 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1525 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1526 # it isn't clear to me exactly what circumstances cause this).
1527 # `zipfile.write()` must be used directly to work around this.
1528 #
1529 # This mess can be avoided if we port to python3.
1530 saved_zip64_limit = zipfile.ZIP64_LIMIT
1531 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1532
1533 if compress_type is None:
1534 compress_type = zip_file.compression
1535 if arcname is None:
1536 arcname = filename
1537
1538 saved_stat = os.stat(filename)
1539
1540 try:
1541 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1542 # file to be zipped and reset it when we're done.
1543 os.chmod(filename, perms)
1544
1545 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001546 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1547 # intentional. zip stores datetimes in local time without a time zone
1548 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1549 # in the zip archive.
1550 local_epoch = datetime.datetime.fromtimestamp(0)
1551 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001552 os.utime(filename, (timestamp, timestamp))
1553
1554 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1555 finally:
1556 os.chmod(filename, saved_stat.st_mode)
1557 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1558 zipfile.ZIP64_LIMIT = saved_zip64_limit
1559
1560
Tao Bao58c1b962015-05-20 09:32:18 -07001561def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001562 compress_type=None):
1563 """Wrap zipfile.writestr() function to work around the zip64 limit.
1564
1565 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1566 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1567 when calling crc32(bytes).
1568
1569 But it still works fine to write a shorter string into a large zip file.
1570 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1571 when we know the string won't be too long.
1572 """
1573
1574 saved_zip64_limit = zipfile.ZIP64_LIMIT
1575 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1576
1577 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1578 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001579 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001580 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001581 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001582 else:
Tao Baof3282b42015-04-01 11:21:55 -07001583 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001584 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1585 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1586 # such a case (since
1587 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1588 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1589 # permission bits. We follow the logic in Python 3 to get consistent
1590 # behavior between using the two versions.
1591 if not zinfo.external_attr:
1592 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001593
1594 # If compress_type is given, it overrides the value in zinfo.
1595 if compress_type is not None:
1596 zinfo.compress_type = compress_type
1597
Tao Bao58c1b962015-05-20 09:32:18 -07001598 # If perms is given, it has a priority.
1599 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001600 # If perms doesn't set the file type, mark it as a regular file.
1601 if perms & 0o770000 == 0:
1602 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001603 zinfo.external_attr = perms << 16
1604
Tao Baof3282b42015-04-01 11:21:55 -07001605 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001606 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1607
Dan Albert8b72aef2015-03-23 19:13:21 -07001608 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001609 zipfile.ZIP64_LIMIT = saved_zip64_limit
1610
1611
Tao Bao89d7ab22017-12-14 17:05:33 -08001612def ZipDelete(zip_filename, entries):
1613 """Deletes entries from a ZIP file.
1614
1615 Since deleting entries from a ZIP file is not supported, it shells out to
1616 'zip -d'.
1617
1618 Args:
1619 zip_filename: The name of the ZIP file.
1620 entries: The name of the entry, or the list of names to be deleted.
1621
1622 Raises:
1623 AssertionError: In case of non-zero return from 'zip'.
1624 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001625 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001626 entries = [entries]
1627 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001628 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001629
1630
Tao Baof3282b42015-04-01 11:21:55 -07001631def ZipClose(zip_file):
1632 # http://b/18015246
1633 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1634 # central directory.
1635 saved_zip64_limit = zipfile.ZIP64_LIMIT
1636 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1637
1638 zip_file.close()
1639
1640 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001641
1642
1643class DeviceSpecificParams(object):
1644 module = None
1645 def __init__(self, **kwargs):
1646 """Keyword arguments to the constructor become attributes of this
1647 object, which is passed to all functions in the device-specific
1648 module."""
1649 for k, v in kwargs.iteritems():
1650 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001651 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001652
1653 if self.module is None:
1654 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001655 if not path:
1656 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001657 try:
1658 if os.path.isdir(path):
1659 info = imp.find_module("releasetools", [path])
1660 else:
1661 d, f = os.path.split(path)
1662 b, x = os.path.splitext(f)
1663 if x == ".py":
1664 f = b
1665 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001666 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001667 self.module = imp.load_module("device_specific", *info)
1668 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001669 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001670
1671 def _DoCall(self, function_name, *args, **kwargs):
1672 """Call the named function in the device-specific module, passing
1673 the given args and kwargs. The first argument to the call will be
1674 the DeviceSpecific object itself. If there is no module, or the
1675 module does not define the function, return the value of the
1676 'default' kwarg (which itself defaults to None)."""
1677 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001678 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001679 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1680
1681 def FullOTA_Assertions(self):
1682 """Called after emitting the block of assertions at the top of a
1683 full OTA package. Implementations can add whatever additional
1684 assertions they like."""
1685 return self._DoCall("FullOTA_Assertions")
1686
Doug Zongkere5ff5902012-01-17 10:55:37 -08001687 def FullOTA_InstallBegin(self):
1688 """Called at the start of full OTA installation."""
1689 return self._DoCall("FullOTA_InstallBegin")
1690
Yifan Hong10c530d2018-12-27 17:34:18 -08001691 def FullOTA_GetBlockDifferences(self):
1692 """Called during full OTA installation and verification.
1693 Implementation should return a list of BlockDifference objects describing
1694 the update on each additional partitions.
1695 """
1696 return self._DoCall("FullOTA_GetBlockDifferences")
1697
Doug Zongker05d3dea2009-06-22 11:32:31 -07001698 def FullOTA_InstallEnd(self):
1699 """Called at the end of full OTA installation; typically this is
1700 used to install the image for the device's baseband processor."""
1701 return self._DoCall("FullOTA_InstallEnd")
1702
1703 def IncrementalOTA_Assertions(self):
1704 """Called after emitting the block of assertions at the top of an
1705 incremental OTA package. Implementations can add whatever
1706 additional assertions they like."""
1707 return self._DoCall("IncrementalOTA_Assertions")
1708
Doug Zongkere5ff5902012-01-17 10:55:37 -08001709 def IncrementalOTA_VerifyBegin(self):
1710 """Called at the start of the verification phase of incremental
1711 OTA installation; additional checks can be placed here to abort
1712 the script before any changes are made."""
1713 return self._DoCall("IncrementalOTA_VerifyBegin")
1714
Doug Zongker05d3dea2009-06-22 11:32:31 -07001715 def IncrementalOTA_VerifyEnd(self):
1716 """Called at the end of the verification phase of incremental OTA
1717 installation; additional checks can be placed here to abort the
1718 script before any changes are made."""
1719 return self._DoCall("IncrementalOTA_VerifyEnd")
1720
Doug Zongkere5ff5902012-01-17 10:55:37 -08001721 def IncrementalOTA_InstallBegin(self):
1722 """Called at the start of incremental OTA installation (after
1723 verification is complete)."""
1724 return self._DoCall("IncrementalOTA_InstallBegin")
1725
Yifan Hong10c530d2018-12-27 17:34:18 -08001726 def IncrementalOTA_GetBlockDifferences(self):
1727 """Called during incremental OTA installation and verification.
1728 Implementation should return a list of BlockDifference objects describing
1729 the update on each additional partitions.
1730 """
1731 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1732
Doug Zongker05d3dea2009-06-22 11:32:31 -07001733 def IncrementalOTA_InstallEnd(self):
1734 """Called at the end of incremental OTA installation; typically
1735 this is used to install the image for the device's baseband
1736 processor."""
1737 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001738
Tao Bao9bc6bb22015-11-09 16:58:28 -08001739 def VerifyOTA_Assertions(self):
1740 return self._DoCall("VerifyOTA_Assertions")
1741
Tao Bao76def242017-11-21 09:25:31 -08001742
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001743class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001744 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001745 self.name = name
1746 self.data = data
1747 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001748 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001749 self.sha1 = sha1(data).hexdigest()
1750
1751 @classmethod
1752 def FromLocalFile(cls, name, diskname):
1753 f = open(diskname, "rb")
1754 data = f.read()
1755 f.close()
1756 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001757
1758 def WriteToTemp(self):
1759 t = tempfile.NamedTemporaryFile()
1760 t.write(self.data)
1761 t.flush()
1762 return t
1763
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001764 def WriteToDir(self, d):
1765 with open(os.path.join(d, self.name), "wb") as fp:
1766 fp.write(self.data)
1767
Geremy Condra36bd3652014-02-06 19:45:10 -08001768 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001769 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001770
Tao Bao76def242017-11-21 09:25:31 -08001771
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001772DIFF_PROGRAM_BY_EXT = {
1773 ".gz" : "imgdiff",
1774 ".zip" : ["imgdiff", "-z"],
1775 ".jar" : ["imgdiff", "-z"],
1776 ".apk" : ["imgdiff", "-z"],
1777 ".img" : "imgdiff",
1778 }
1779
Tao Bao76def242017-11-21 09:25:31 -08001780
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001781class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001782 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001783 self.tf = tf
1784 self.sf = sf
1785 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001786 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001787
1788 def ComputePatch(self):
1789 """Compute the patch (as a string of data) needed to turn sf into
1790 tf. Returns the same tuple as GetPatch()."""
1791
1792 tf = self.tf
1793 sf = self.sf
1794
Doug Zongker24cd2802012-08-14 16:36:15 -07001795 if self.diff_program:
1796 diff_program = self.diff_program
1797 else:
1798 ext = os.path.splitext(tf.name)[1]
1799 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001800
1801 ttemp = tf.WriteToTemp()
1802 stemp = sf.WriteToTemp()
1803
1804 ext = os.path.splitext(tf.name)[1]
1805
1806 try:
1807 ptemp = tempfile.NamedTemporaryFile()
1808 if isinstance(diff_program, list):
1809 cmd = copy.copy(diff_program)
1810 else:
1811 cmd = [diff_program]
1812 cmd.append(stemp.name)
1813 cmd.append(ttemp.name)
1814 cmd.append(ptemp.name)
1815 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001816 err = []
1817 def run():
1818 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001819 if e:
1820 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001821 th = threading.Thread(target=run)
1822 th.start()
1823 th.join(timeout=300) # 5 mins
1824 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001825 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001826 p.terminate()
1827 th.join(5)
1828 if th.is_alive():
1829 p.kill()
1830 th.join()
1831
Tianjie Xua2a9f992018-01-05 15:15:54 -08001832 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001833 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001834 self.patch = None
1835 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001836 diff = ptemp.read()
1837 finally:
1838 ptemp.close()
1839 stemp.close()
1840 ttemp.close()
1841
1842 self.patch = diff
1843 return self.tf, self.sf, self.patch
1844
1845
1846 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001847 """Returns a tuple of (target_file, source_file, patch_data).
1848
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001849 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001850 computing the patch failed.
1851 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001852 return self.tf, self.sf, self.patch
1853
1854
1855def ComputeDifferences(diffs):
1856 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001857 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001858
1859 # Do the largest files first, to try and reduce the long-pole effect.
1860 by_size = [(i.tf.size, i) for i in diffs]
1861 by_size.sort(reverse=True)
1862 by_size = [i[1] for i in by_size]
1863
1864 lock = threading.Lock()
1865 diff_iter = iter(by_size) # accessed under lock
1866
1867 def worker():
1868 try:
1869 lock.acquire()
1870 for d in diff_iter:
1871 lock.release()
1872 start = time.time()
1873 d.ComputePatch()
1874 dur = time.time() - start
1875 lock.acquire()
1876
1877 tf, sf, patch = d.GetPatch()
1878 if sf.name == tf.name:
1879 name = tf.name
1880 else:
1881 name = "%s (%s)" % (tf.name, sf.name)
1882 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001883 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001884 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001885 logger.info(
1886 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1887 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001888 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001889 except Exception:
1890 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001891 raise
1892
1893 # start worker threads; wait for them all to finish.
1894 threads = [threading.Thread(target=worker)
1895 for i in range(OPTIONS.worker_threads)]
1896 for th in threads:
1897 th.start()
1898 while threads:
1899 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001900
1901
Dan Albert8b72aef2015-03-23 19:13:21 -07001902class BlockDifference(object):
1903 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001904 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001905 self.tgt = tgt
1906 self.src = src
1907 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001908 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001909 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001910
Tao Baodd2a5892015-03-12 12:32:37 -07001911 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001912 version = max(
1913 int(i) for i in
1914 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001915 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001916 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001917
1918 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001919 version=self.version,
1920 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001921 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001922 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001923 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001924 self.touched_src_ranges = b.touched_src_ranges
1925 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001926
Yifan Hong10c530d2018-12-27 17:34:18 -08001927 # On devices with dynamic partitions, for new partitions,
1928 # src is None but OPTIONS.source_info_dict is not.
1929 if OPTIONS.source_info_dict is None:
1930 is_dynamic_build = OPTIONS.info_dict.get(
1931 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001932 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001933 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001934 is_dynamic_build = OPTIONS.source_info_dict.get(
1935 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001936 is_dynamic_source = partition in shlex.split(
1937 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001938
Yifan Hongbb2658d2019-01-25 12:30:58 -08001939 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001940 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1941
Yifan Hongbb2658d2019-01-25 12:30:58 -08001942 # For dynamic partitions builds, check partition list in both source
1943 # and target build because new partitions may be added, and existing
1944 # partitions may be removed.
1945 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1946
Yifan Hong10c530d2018-12-27 17:34:18 -08001947 if is_dynamic:
1948 self.device = 'map_partition("%s")' % partition
1949 else:
1950 if OPTIONS.source_info_dict is None:
1951 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1952 else:
1953 _, device_path = GetTypeAndDevice("/" + partition,
1954 OPTIONS.source_info_dict)
1955 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001956
Tao Baod8d14be2016-02-04 14:26:02 -08001957 @property
1958 def required_cache(self):
1959 return self._required_cache
1960
Tao Bao76def242017-11-21 09:25:31 -08001961 def WriteScript(self, script, output_zip, progress=None,
1962 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001963 if not self.src:
1964 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001965 script.Print("Patching %s image unconditionally..." % (self.partition,))
1966 else:
1967 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001968
Dan Albert8b72aef2015-03-23 19:13:21 -07001969 if progress:
1970 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001971 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001972
1973 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001974 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001975
Tao Bao9bc6bb22015-11-09 16:58:28 -08001976 def WriteStrictVerifyScript(self, script):
1977 """Verify all the blocks in the care_map, including clobbered blocks.
1978
1979 This differs from the WriteVerifyScript() function: a) it prints different
1980 error messages; b) it doesn't allow half-way updated images to pass the
1981 verification."""
1982
1983 partition = self.partition
1984 script.Print("Verifying %s..." % (partition,))
1985 ranges = self.tgt.care_map
1986 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001987 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001988 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1989 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001990 self.device, ranges_str,
1991 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001992 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001993 script.AppendExtra("")
1994
Tao Baod522bdc2016-04-12 15:53:16 -07001995 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001996 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001997
1998 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001999 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002000 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002001
2002 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002003 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002004 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002005 ranges = self.touched_src_ranges
2006 expected_sha1 = self.touched_src_sha1
2007 else:
2008 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2009 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002010
2011 # No blocks to be checked, skipping.
2012 if not ranges:
2013 return
2014
Tao Bao5ece99d2015-05-12 11:42:31 -07002015 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002016 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002017 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002018 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2019 '"%s.patch.dat")) then' % (
2020 self.device, ranges_str, expected_sha1,
2021 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002022 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002023 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002024
Tianjie Xufc3422a2015-12-15 11:53:59 -08002025 if self.version >= 4:
2026
2027 # Bug: 21124327
2028 # When generating incrementals for the system and vendor partitions in
2029 # version 4 or newer, explicitly check the first block (which contains
2030 # the superblock) of the partition to see if it's what we expect. If
2031 # this check fails, give an explicit log message about the partition
2032 # having been remounted R/W (the most likely explanation).
2033 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002034 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002035
2036 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002037 if partition == "system":
2038 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2039 else:
2040 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002041 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002042 'ifelse (block_image_recover({device}, "{ranges}") && '
2043 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002044 'package_extract_file("{partition}.transfer.list"), '
2045 '"{partition}.new.dat", "{partition}.patch.dat"), '
2046 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002047 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002048 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002049 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002050
Tao Baodd2a5892015-03-12 12:32:37 -07002051 # Abort the OTA update. Note that the incremental OTA cannot be applied
2052 # even if it may match the checksum of the target partition.
2053 # a) If version < 3, operations like move and erase will make changes
2054 # unconditionally and damage the partition.
2055 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002056 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002057 if partition == "system":
2058 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2059 else:
2060 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2061 script.AppendExtra((
2062 'abort("E%d: %s partition has unexpected contents");\n'
2063 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002064
Yifan Hong10c530d2018-12-27 17:34:18 -08002065 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002066 partition = self.partition
2067 script.Print('Verifying the updated %s image...' % (partition,))
2068 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2069 ranges = self.tgt.care_map
2070 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002071 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002072 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002073 self.device, ranges_str,
2074 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002075
2076 # Bug: 20881595
2077 # Verify that extended blocks are really zeroed out.
2078 if self.tgt.extended:
2079 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002080 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002081 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002082 self.device, ranges_str,
2083 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002084 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002085 if partition == "system":
2086 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2087 else:
2088 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002089 script.AppendExtra(
2090 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002091 ' abort("E%d: %s partition has unexpected non-zero contents after '
2092 'OTA update");\n'
2093 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002094 else:
2095 script.Print('Verified the updated %s image.' % (partition,))
2096
Tianjie Xu209db462016-05-24 17:34:52 -07002097 if partition == "system":
2098 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2099 else:
2100 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2101
Tao Bao5fcaaef2015-06-01 13:40:49 -07002102 script.AppendExtra(
2103 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002104 ' abort("E%d: %s partition has unexpected contents after OTA '
2105 'update");\n'
2106 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002107
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002108 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002109 ZipWrite(output_zip,
2110 '{}.transfer.list'.format(self.path),
2111 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002112
Tao Bao76def242017-11-21 09:25:31 -08002113 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2114 # its size. Quailty 9 almost triples the compression time but doesn't
2115 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002116 # zip | brotli(quality 6) | brotli(quality 9)
2117 # compressed_size: 942M | 869M (~8% reduced) | 854M
2118 # compression_time: 75s | 265s | 719s
2119 # decompression_time: 15s | 25s | 25s
2120
2121 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002122 brotli_cmd = ['brotli', '--quality=6',
2123 '--output={}.new.dat.br'.format(self.path),
2124 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002125 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002126 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002127
2128 new_data_name = '{}.new.dat.br'.format(self.partition)
2129 ZipWrite(output_zip,
2130 '{}.new.dat.br'.format(self.path),
2131 new_data_name,
2132 compress_type=zipfile.ZIP_STORED)
2133 else:
2134 new_data_name = '{}.new.dat'.format(self.partition)
2135 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2136
Dan Albert8e0178d2015-01-27 15:53:15 -08002137 ZipWrite(output_zip,
2138 '{}.patch.dat'.format(self.path),
2139 '{}.patch.dat'.format(self.partition),
2140 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002141
Tianjie Xu209db462016-05-24 17:34:52 -07002142 if self.partition == "system":
2143 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2144 else:
2145 code = ErrorCode.VENDOR_UPDATE_FAILURE
2146
Yifan Hong10c530d2018-12-27 17:34:18 -08002147 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002148 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002149 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002150 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002151 device=self.device, partition=self.partition,
2152 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002153 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002154
Dan Albert8b72aef2015-03-23 19:13:21 -07002155 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002156 data = source.ReadRangeSet(ranges)
2157 ctx = sha1()
2158
2159 for p in data:
2160 ctx.update(p)
2161
2162 return ctx.hexdigest()
2163
Tao Baoe9b61912015-07-09 17:37:49 -07002164 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2165 """Return the hash value for all zero blocks."""
2166 zero_block = '\x00' * 4096
2167 ctx = sha1()
2168 for _ in range(num_blocks):
2169 ctx.update(zero_block)
2170
2171 return ctx.hexdigest()
2172
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002173
2174DataImage = blockimgdiff.DataImage
Yifan Hong8a66a712019-04-04 15:37:57 -07002175EmptyImage = blockimgdiff.EmptyImage
Tao Bao76def242017-11-21 09:25:31 -08002176
Doug Zongker96a57e72010-09-26 14:57:41 -07002177# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002178PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002179 "ext4": "EMMC",
2180 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002181 "f2fs": "EMMC",
2182 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002183}
Doug Zongker96a57e72010-09-26 14:57:41 -07002184
Tao Bao76def242017-11-21 09:25:31 -08002185
Doug Zongker96a57e72010-09-26 14:57:41 -07002186def GetTypeAndDevice(mount_point, info):
2187 fstab = info["fstab"]
2188 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002189 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2190 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002191 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002192 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002193
2194
2195def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002196 """Parses and converts a PEM-encoded certificate into DER-encoded.
2197
2198 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2199
2200 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002201 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002202 """
2203 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002204 save = False
2205 for line in data.split("\n"):
2206 if "--END CERTIFICATE--" in line:
2207 break
2208 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002209 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002210 if "--BEGIN CERTIFICATE--" in line:
2211 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002212 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002213 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002214
Tao Bao04e1f012018-02-04 12:13:35 -08002215
2216def ExtractPublicKey(cert):
2217 """Extracts the public key (PEM-encoded) from the given certificate file.
2218
2219 Args:
2220 cert: The certificate filename.
2221
2222 Returns:
2223 The public key string.
2224
2225 Raises:
2226 AssertionError: On non-zero return from 'openssl'.
2227 """
2228 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2229 # While openssl 1.1 writes the key into the given filename followed by '-out',
2230 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2231 # stdout instead.
2232 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2233 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2234 pubkey, stderrdata = proc.communicate()
2235 assert proc.returncode == 0, \
2236 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2237 return pubkey
2238
2239
Tao Bao2cc0ca12019-03-15 10:44:43 -07002240def ExtractAvbPublicKey(key):
2241 """Extracts the AVB public key from the given public or private key.
2242
2243 Args:
2244 key: The input key file, which should be PEM-encoded public or private key.
2245
2246 Returns:
2247 The path to the extracted AVB public key file.
2248 """
2249 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2250 RunAndCheckOutput(
2251 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2252 return output
2253
2254
Doug Zongker412c02f2014-02-13 10:58:24 -08002255def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2256 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002257 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002258
Tao Bao6d5d6232018-03-09 17:04:42 -08002259 Most of the space in the boot and recovery images is just the kernel, which is
2260 identical for the two, so the resulting patch should be efficient. Add it to
2261 the output zip, along with a shell script that is run from init.rc on first
2262 boot to actually do the patching and install the new recovery image.
2263
2264 Args:
2265 input_dir: The top-level input directory of the target-files.zip.
2266 output_sink: The callback function that writes the result.
2267 recovery_img: File object for the recovery image.
2268 boot_img: File objects for the boot image.
2269 info_dict: A dict returned by common.LoadInfoDict() on the input
2270 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002271 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002272 if info_dict is None:
2273 info_dict = OPTIONS.info_dict
2274
Tao Bao6d5d6232018-03-09 17:04:42 -08002275 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002276
Tao Baof2cffbd2015-07-22 12:33:18 -07002277 if full_recovery_image:
2278 output_sink("etc/recovery.img", recovery_img.data)
2279
2280 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002281 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002282 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002283 # With system-root-image, boot and recovery images will have mismatching
2284 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2285 # to handle such a case.
2286 if system_root_image:
2287 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002288 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002289 assert not os.path.exists(path)
2290 else:
2291 diff_program = ["imgdiff"]
2292 if os.path.exists(path):
2293 diff_program.append("-b")
2294 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002295 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002296 else:
2297 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002298
2299 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2300 _, _, patch = d.ComputePatch()
2301 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002302
Dan Albertebb19aa2015-03-27 19:11:53 -07002303 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002304 # The following GetTypeAndDevice()s need to use the path in the target
2305 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002306 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2307 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2308 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002309 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002310
Tao Baof2cffbd2015-07-22 12:33:18 -07002311 if full_recovery_image:
2312 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002313if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2314 applypatch \\
2315 --flash /system/etc/recovery.img \\
2316 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2317 log -t recovery "Installing new recovery image: succeeded" || \\
2318 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002319else
2320 log -t recovery "Recovery image already installed"
2321fi
2322""" % {'type': recovery_type,
2323 'device': recovery_device,
2324 'sha1': recovery_img.sha1,
2325 'size': recovery_img.size}
2326 else:
2327 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002328if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2329 applypatch %(bonus_args)s \\
2330 --patch /system/recovery-from-boot.p \\
2331 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2332 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2333 log -t recovery "Installing new recovery image: succeeded" || \\
2334 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002335else
2336 log -t recovery "Recovery image already installed"
2337fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002338""" % {'boot_size': boot_img.size,
2339 'boot_sha1': boot_img.sha1,
2340 'recovery_size': recovery_img.size,
2341 'recovery_sha1': recovery_img.sha1,
2342 'boot_type': boot_type,
2343 'boot_device': boot_device,
2344 'recovery_type': recovery_type,
2345 'recovery_device': recovery_device,
2346 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002347
2348 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002349 # in the L release.
2350 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002351
Tao Bao32fcdab2018-10-12 10:30:39 -07002352 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002353
Tao Baoda30cfa2017-12-01 16:19:46 -08002354 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002355
2356
2357class DynamicPartitionUpdate(object):
2358 def __init__(self, src_group=None, tgt_group=None, progress=None,
2359 block_difference=None):
2360 self.src_group = src_group
2361 self.tgt_group = tgt_group
2362 self.progress = progress
2363 self.block_difference = block_difference
2364
2365 @property
2366 def src_size(self):
2367 if not self.block_difference:
2368 return 0
2369 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2370
2371 @property
2372 def tgt_size(self):
2373 if not self.block_difference:
2374 return 0
2375 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2376
2377 @staticmethod
2378 def _GetSparseImageSize(img):
2379 if not img:
2380 return 0
2381 return img.blocksize * img.total_blocks
2382
2383
2384class DynamicGroupUpdate(object):
2385 def __init__(self, src_size=None, tgt_size=None):
2386 # None: group does not exist. 0: no size limits.
2387 self.src_size = src_size
2388 self.tgt_size = tgt_size
2389
2390
2391class DynamicPartitionsDifference(object):
2392 def __init__(self, info_dict, block_diffs, progress_dict=None,
2393 source_info_dict=None):
2394 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002395 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002396
2397 self._remove_all_before_apply = False
2398 if source_info_dict is None:
2399 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002400 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002401
Tao Baof1113e92019-06-18 12:10:14 -07002402 block_diff_dict = collections.OrderedDict(
2403 [(e.partition, e) for e in block_diffs])
2404
Yifan Hong10c530d2018-12-27 17:34:18 -08002405 assert len(block_diff_dict) == len(block_diffs), \
2406 "Duplicated BlockDifference object for {}".format(
2407 [partition for partition, count in
2408 collections.Counter(e.partition for e in block_diffs).items()
2409 if count > 1])
2410
Yifan Hong79997e52019-01-23 16:56:19 -08002411 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002412
2413 for p, block_diff in block_diff_dict.items():
2414 self._partition_updates[p] = DynamicPartitionUpdate()
2415 self._partition_updates[p].block_difference = block_diff
2416
2417 for p, progress in progress_dict.items():
2418 if p in self._partition_updates:
2419 self._partition_updates[p].progress = progress
2420
2421 tgt_groups = shlex.split(info_dict.get(
2422 "super_partition_groups", "").strip())
2423 src_groups = shlex.split(source_info_dict.get(
2424 "super_partition_groups", "").strip())
2425
2426 for g in tgt_groups:
2427 for p in shlex.split(info_dict.get(
2428 "super_%s_partition_list" % g, "").strip()):
2429 assert p in self._partition_updates, \
2430 "{} is in target super_{}_partition_list but no BlockDifference " \
2431 "object is provided.".format(p, g)
2432 self._partition_updates[p].tgt_group = g
2433
2434 for g in src_groups:
2435 for p in shlex.split(source_info_dict.get(
2436 "super_%s_partition_list" % g, "").strip()):
2437 assert p in self._partition_updates, \
2438 "{} is in source super_{}_partition_list but no BlockDifference " \
2439 "object is provided.".format(p, g)
2440 self._partition_updates[p].src_group = g
2441
Yifan Hong45433e42019-01-18 13:55:25 -08002442 target_dynamic_partitions = set(shlex.split(info_dict.get(
2443 "dynamic_partition_list", "").strip()))
2444 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2445 if u.tgt_size)
2446 assert block_diffs_with_target == target_dynamic_partitions, \
2447 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2448 list(target_dynamic_partitions), list(block_diffs_with_target))
2449
2450 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2451 "dynamic_partition_list", "").strip()))
2452 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2453 if u.src_size)
2454 assert block_diffs_with_source == source_dynamic_partitions, \
2455 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2456 list(source_dynamic_partitions), list(block_diffs_with_source))
2457
Yifan Hong10c530d2018-12-27 17:34:18 -08002458 if self._partition_updates:
2459 logger.info("Updating dynamic partitions %s",
2460 self._partition_updates.keys())
2461
Yifan Hong79997e52019-01-23 16:56:19 -08002462 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002463
2464 for g in tgt_groups:
2465 self._group_updates[g] = DynamicGroupUpdate()
2466 self._group_updates[g].tgt_size = int(info_dict.get(
2467 "super_%s_group_size" % g, "0").strip())
2468
2469 for g in src_groups:
2470 if g not in self._group_updates:
2471 self._group_updates[g] = DynamicGroupUpdate()
2472 self._group_updates[g].src_size = int(source_info_dict.get(
2473 "super_%s_group_size" % g, "0").strip())
2474
2475 self._Compute()
2476
2477 def WriteScript(self, script, output_zip, write_verify_script=False):
2478 script.Comment('--- Start patching dynamic partitions ---')
2479 for p, u in self._partition_updates.items():
2480 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2481 script.Comment('Patch partition %s' % p)
2482 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2483 write_verify_script=False)
2484
2485 op_list_path = MakeTempFile()
2486 with open(op_list_path, 'w') as f:
2487 for line in self._op_list:
2488 f.write('{}\n'.format(line))
2489
2490 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2491
2492 script.Comment('Update dynamic partition metadata')
2493 script.AppendExtra('assert(update_dynamic_partitions('
2494 'package_extract_file("dynamic_partitions_op_list")));')
2495
2496 if write_verify_script:
2497 for p, u in self._partition_updates.items():
2498 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2499 u.block_difference.WritePostInstallVerifyScript(script)
2500 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2501
2502 for p, u in self._partition_updates.items():
2503 if u.tgt_size and u.src_size <= u.tgt_size:
2504 script.Comment('Patch partition %s' % p)
2505 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2506 write_verify_script=write_verify_script)
2507 if write_verify_script:
2508 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2509
2510 script.Comment('--- End patching dynamic partitions ---')
2511
2512 def _Compute(self):
2513 self._op_list = list()
2514
2515 def append(line):
2516 self._op_list.append(line)
2517
2518 def comment(line):
2519 self._op_list.append("# %s" % line)
2520
2521 if self._remove_all_before_apply:
2522 comment('Remove all existing dynamic partitions and groups before '
2523 'applying full OTA')
2524 append('remove_all_groups')
2525
2526 for p, u in self._partition_updates.items():
2527 if u.src_group and not u.tgt_group:
2528 append('remove %s' % p)
2529
2530 for p, u in self._partition_updates.items():
2531 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2532 comment('Move partition %s from %s to default' % (p, u.src_group))
2533 append('move %s default' % p)
2534
2535 for p, u in self._partition_updates.items():
2536 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2537 comment('Shrink partition %s from %d to %d' %
2538 (p, u.src_size, u.tgt_size))
2539 append('resize %s %s' % (p, u.tgt_size))
2540
2541 for g, u in self._group_updates.items():
2542 if u.src_size is not None and u.tgt_size is None:
2543 append('remove_group %s' % g)
2544 if (u.src_size is not None and u.tgt_size is not None and
2545 u.src_size > u.tgt_size):
2546 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2547 append('resize_group %s %d' % (g, u.tgt_size))
2548
2549 for g, u in self._group_updates.items():
2550 if u.src_size is None and u.tgt_size is not None:
2551 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2552 append('add_group %s %d' % (g, u.tgt_size))
2553 if (u.src_size is not None and u.tgt_size is not None and
2554 u.src_size < u.tgt_size):
2555 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2556 append('resize_group %s %d' % (g, u.tgt_size))
2557
2558 for p, u in self._partition_updates.items():
2559 if u.tgt_group and not u.src_group:
2560 comment('Add partition %s to group %s' % (p, u.tgt_group))
2561 append('add %s %s' % (p, u.tgt_group))
2562
2563 for p, u in self._partition_updates.items():
2564 if u.tgt_size and u.src_size < u.tgt_size:
2565 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2566 append('resize %s %d' % (p, u.tgt_size))
2567
2568 for p, u in self._partition_updates.items():
2569 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2570 comment('Move partition %s from default to %s' %
2571 (p, u.tgt_group))
2572 append('move %s %s' % (p, u.tgt_group))