blob: edde89c525469636f7c91faf27d7ca876814dec5 [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
Tao Baoa3705452019-06-24 15:33:41 -070057 # Python >= 3.3 returns 'linux', whereas Python 2.7 gives 'linux2'.
Dan Albert8b72aef2015-03-23 19:13:21 -070058 platform_search_path = {
Tao Baoa3705452019-06-24 15:33:41 -070059 "linux": os.path.join(base_search_path, "host/linux-x86"),
Pavel Salomatov32676552019-03-06 20:00:45 +030060 "linux2": os.path.join(base_search_path, "host/linux-x86"),
61 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070062 }
Doug Zongker85448772014-09-09 14:59:20 -070063
Tao Bao76def242017-11-21 09:25:31 -080064 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070065 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080066 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.extra_signapk_args = []
68 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080069 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070070 self.public_key_suffix = ".x509.pem"
71 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070072 # use otatools built boot_signer by default
73 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070074 self.boot_signer_args = []
75 self.verity_signer_path = None
76 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070077 self.verbose = False
78 self.tempfiles = []
79 self.device_specific = None
80 self.extras = {}
81 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070082 self.source_info_dict = None
83 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070084 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070085 # Stash size cannot exceed cache_size * threshold.
86 self.cache_size = None
87 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070088
89
90OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070091
Tao Bao71197512018-10-11 14:08:45 -070092# The block size that's used across the releasetools scripts.
93BLOCK_SIZE = 4096
94
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080095# Values for "certificate" in apkcerts that mean special things.
96SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
97
Tao Bao5cc0abb2019-03-21 10:18:05 -070098# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
99# that system_other is not in the list because we don't want to include its
100# descriptor into vbmeta.img.
101AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'product_services',
102 'recovery', 'system', 'vendor')
Tao Bao9dd909e2017-11-14 11:27:32 -0800103
Tao Bao08c190f2019-06-03 23:07:58 -0700104# Chained VBMeta partitions.
105AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
106
Tianjie Xu861f4132018-09-12 11:49:33 -0700107# Partitions that should have their care_map added to META/care_map.pb
108PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
109 'odm')
110
111
Tianjie Xu209db462016-05-24 17:34:52 -0700112class ErrorCode(object):
113 """Define error_codes for failures that happen during the actual
114 update package installation.
115
116 Error codes 0-999 are reserved for failures before the package
117 installation (i.e. low battery, package verification failure).
118 Detailed code in 'bootable/recovery/error_code.h' """
119
120 SYSTEM_VERIFICATION_FAILURE = 1000
121 SYSTEM_UPDATE_FAILURE = 1001
122 SYSTEM_UNEXPECTED_CONTENTS = 1002
123 SYSTEM_NONZERO_CONTENTS = 1003
124 SYSTEM_RECOVER_FAILURE = 1004
125 VENDOR_VERIFICATION_FAILURE = 2000
126 VENDOR_UPDATE_FAILURE = 2001
127 VENDOR_UNEXPECTED_CONTENTS = 2002
128 VENDOR_NONZERO_CONTENTS = 2003
129 VENDOR_RECOVER_FAILURE = 2004
130 OEM_PROP_MISMATCH = 3000
131 FINGERPRINT_MISMATCH = 3001
132 THUMBPRINT_MISMATCH = 3002
133 OLDER_BUILD = 3003
134 DEVICE_MISMATCH = 3004
135 BAD_PATCH_FILE = 3005
136 INSUFFICIENT_CACHE_SPACE = 3006
137 TUNE_PARTITION_FAILURE = 3007
138 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800139
Tao Bao80921982018-03-21 21:02:19 -0700140
Dan Albert8b72aef2015-03-23 19:13:21 -0700141class ExternalError(RuntimeError):
142 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700143
144
Tao Bao32fcdab2018-10-12 10:30:39 -0700145def InitLogging():
146 DEFAULT_LOGGING_CONFIG = {
147 'version': 1,
148 'disable_existing_loggers': False,
149 'formatters': {
150 'standard': {
151 'format':
152 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
153 'datefmt': '%Y-%m-%d %H:%M:%S',
154 },
155 },
156 'handlers': {
157 'default': {
158 'class': 'logging.StreamHandler',
159 'formatter': 'standard',
160 },
161 },
162 'loggers': {
163 '': {
164 'handlers': ['default'],
165 'level': 'WARNING',
166 'propagate': True,
167 }
168 }
169 }
170 env_config = os.getenv('LOGGING_CONFIG')
171 if env_config:
172 with open(env_config) as f:
173 config = json.load(f)
174 else:
175 config = DEFAULT_LOGGING_CONFIG
176
177 # Increase the logging level for verbose mode.
178 if OPTIONS.verbose:
179 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
180 config['loggers']['']['level'] = 'INFO'
181
182 logging.config.dictConfig(config)
183
184
Tao Bao39451582017-05-04 11:10:47 -0700185def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700186 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700187
Tao Bao73dd4f42018-10-04 16:25:33 -0700188 Args:
189 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700190 verbose: Whether the commands should be shown. Default to the global
191 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700192 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
193 stdin, etc. stdout and stderr will default to subprocess.PIPE and
194 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800195 universal_newlines will default to True, as most of the users in
196 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700197
198 Returns:
199 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700200 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700201 if 'stdout' not in kwargs and 'stderr' not in kwargs:
202 kwargs['stdout'] = subprocess.PIPE
203 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800204 if 'universal_newlines' not in kwargs:
205 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700206 # Don't log any if caller explicitly says so.
207 if verbose != False:
208 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700209 return subprocess.Popen(args, **kwargs)
210
211
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800212def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800213 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800214
215 Args:
216 args: The command represented as a list of strings.
217 verbose: Whether the commands should be shown. Default to the global
218 verbosity if unspecified.
219 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
220 stdin, etc. stdout and stderr will default to subprocess.PIPE and
221 subprocess.STDOUT respectively unless caller specifies any of them.
222
Bill Peckham889b0c62019-02-21 18:53:37 -0800223 Raises:
224 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800225 """
226 proc = Run(args, verbose=verbose, **kwargs)
227 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800228
229 if proc.returncode != 0:
230 raise ExternalError(
231 "Failed to run command '{}' (exit code {})".format(
232 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800233
234
Tao Bao986ee862018-10-04 15:46:16 -0700235def RunAndCheckOutput(args, verbose=None, **kwargs):
236 """Runs the given command and returns the output.
237
238 Args:
239 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700240 verbose: Whether the commands should be shown. Default to the global
241 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700242 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
243 stdin, etc. stdout and stderr will default to subprocess.PIPE and
244 subprocess.STDOUT respectively unless caller specifies any of them.
245
246 Returns:
247 The output string.
248
249 Raises:
250 ExternalError: On non-zero exit from the command.
251 """
Tao Bao986ee862018-10-04 15:46:16 -0700252 proc = Run(args, verbose=verbose, **kwargs)
253 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700254 # Don't log any if caller explicitly says so.
255 if verbose != False:
256 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700257 if proc.returncode != 0:
258 raise ExternalError(
259 "Failed to run command '{}' (exit code {}):\n{}".format(
260 args, proc.returncode, output))
261 return output
262
263
Tao Baoc765cca2018-01-31 17:32:40 -0800264def RoundUpTo4K(value):
265 rounded_up = value + 4095
266 return rounded_up - (rounded_up % 4096)
267
268
Ying Wang7e6d4e42010-12-13 16:25:36 -0800269def CloseInheritedPipes():
270 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
271 before doing other work."""
272 if platform.system() != "Darwin":
273 return
274 for d in range(3, 1025):
275 try:
276 stat = os.fstat(d)
277 if stat is not None:
278 pipebit = stat[0] & 0x1000
279 if pipebit != 0:
280 os.close(d)
281 except OSError:
282 pass
283
284
Tao Bao410ad8b2018-08-24 12:08:38 -0700285def LoadInfoDict(input_file, repacking=False):
286 """Loads the key/value pairs from the given input target_files.
287
288 It reads `META/misc_info.txt` file in the target_files input, does sanity
289 checks and returns the parsed key/value pairs for to the given build. It's
290 usually called early when working on input target_files files, e.g. when
291 generating OTAs, or signing builds. Note that the function may be called
292 against an old target_files file (i.e. from past dessert releases). So the
293 property parsing needs to be backward compatible.
294
295 In a `META/misc_info.txt`, a few properties are stored as links to the files
296 in the PRODUCT_OUT directory. It works fine with the build system. However,
297 they are no longer available when (re)generating images from target_files zip.
298 When `repacking` is True, redirect these properties to the actual files in the
299 unzipped directory.
300
301 Args:
302 input_file: The input target_files file, which could be an open
303 zipfile.ZipFile instance, or a str for the dir that contains the files
304 unzipped from a target_files file.
305 repacking: Whether it's trying repack an target_files file after loading the
306 info dict (default: False). If so, it will rewrite a few loaded
307 properties (e.g. selinux_fc, root_dir) to point to the actual files in
308 target_files file. When doing repacking, `input_file` must be a dir.
309
310 Returns:
311 A dict that contains the parsed key/value pairs.
312
313 Raises:
314 AssertionError: On invalid input arguments.
315 ValueError: On malformed input values.
316 """
317 if repacking:
318 assert isinstance(input_file, str), \
319 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700320
Doug Zongkerc9253822014-02-04 12:17:58 -0800321 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700322 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800323 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800324 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700325 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800326 try:
327 with open(path) as f:
328 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700329 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800330 if e.errno == errno.ENOENT:
331 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800332
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700333 try:
Michael Runge6e836112014-04-15 17:40:21 -0700334 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700335 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700336 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700337
Tao Bao410ad8b2018-08-24 12:08:38 -0700338 if "recovery_api_version" not in d:
339 raise ValueError("Failed to find 'recovery_api_version'")
340 if "fstab_version" not in d:
341 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800342
Tao Bao410ad8b2018-08-24 12:08:38 -0700343 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700344 # "selinux_fc" properties should point to the file_contexts files
345 # (file_contexts.bin) under META/.
346 for key in d:
347 if key.endswith("selinux_fc"):
348 fc_basename = os.path.basename(d[key])
349 fc_config = os.path.join(input_file, "META", fc_basename)
350 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700351
Daniel Norman72c626f2019-05-13 15:58:14 -0700352 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700353
Tom Cherryd14b8952018-08-09 14:26:00 -0700354 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700355 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700356 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700357 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700358
Tao Baof54216f2016-03-29 15:12:37 -0700359 # Redirect {system,vendor}_base_fs_file.
360 if "system_base_fs_file" in d:
361 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700362 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700363 if os.path.exists(system_base_fs_file):
364 d["system_base_fs_file"] = system_base_fs_file
365 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700366 logger.warning(
367 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700368 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700369
370 if "vendor_base_fs_file" in d:
371 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700372 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700373 if os.path.exists(vendor_base_fs_file):
374 d["vendor_base_fs_file"] = vendor_base_fs_file
375 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700376 logger.warning(
377 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700378 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700379
Doug Zongker37974732010-09-16 17:44:38 -0700380 def makeint(key):
381 if key in d:
382 d[key] = int(d[key], 0)
383
384 makeint("recovery_api_version")
385 makeint("blocksize")
386 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700387 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700388 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700389 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700390 makeint("recovery_size")
391 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800392 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700393
Tao Baoa57ab9f2018-08-24 12:08:38 -0700394 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
395 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
396 # cases, since it may load the info_dict from an old build (e.g. when
397 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800398 system_root_image = d.get("system_root_image") == "true"
399 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700400 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700401 if isinstance(input_file, zipfile.ZipFile):
402 if recovery_fstab_path not in input_file.namelist():
403 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
404 else:
405 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
406 if not os.path.exists(path):
407 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800408 d["fstab"] = LoadRecoveryFSTab(
409 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700410
Tao Bao76def242017-11-21 09:25:31 -0800411 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700412 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700413 if isinstance(input_file, zipfile.ZipFile):
414 if recovery_fstab_path not in input_file.namelist():
415 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
416 else:
417 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
418 if not os.path.exists(path):
419 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800420 d["fstab"] = LoadRecoveryFSTab(
421 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700422
Tianjie Xucfa86222016-03-07 16:31:19 -0800423 else:
424 d["fstab"] = None
425
Tianjie Xu861f4132018-09-12 11:49:33 -0700426 # Tries to load the build props for all partitions with care_map, including
427 # system and vendor.
428 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800429 partition_prop = "{}.build.prop".format(partition)
430 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700431 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800432 # Some partition might use /<partition>/etc/build.prop as the new path.
433 # TODO: try new path first when majority of them switch to the new path.
434 if not d[partition_prop]:
435 d[partition_prop] = LoadBuildProp(
436 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700437 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800438
439 # Set up the salt (based on fingerprint or thumbprint) that will be used when
440 # adding AVB footer.
441 if d.get("avb_enable") == "true":
442 fp = None
443 if "build.prop" in d:
444 build_prop = d["build.prop"]
445 if "ro.build.fingerprint" in build_prop:
446 fp = build_prop["ro.build.fingerprint"]
447 elif "ro.build.thumbprint" in build_prop:
448 fp = build_prop["ro.build.thumbprint"]
449 if fp:
450 d["avb_salt"] = sha256(fp).hexdigest()
451
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700452 return d
453
Tao Baod1de6f32017-03-01 16:38:48 -0800454
Tao Baobcd1d162017-08-26 13:10:26 -0700455def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700456 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700457 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700458 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700459 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700460 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700461 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700462
Tao Baod1de6f32017-03-01 16:38:48 -0800463
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900464def LoadDictionaryFromFile(file_path):
465 with open(file_path) as f:
466 lines = list(f.read().splitlines())
467
468 return LoadDictionaryFromLines(lines)
469
470
Michael Runge6e836112014-04-15 17:40:21 -0700471def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700472 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700473 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700474 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700475 if not line or line.startswith("#"):
476 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700477 if "=" in line:
478 name, value = line.split("=", 1)
479 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700480 return d
481
Tao Baod1de6f32017-03-01 16:38:48 -0800482
Tianjie Xucfa86222016-03-07 16:31:19 -0800483def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
484 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700485 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800486 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700487 self.mount_point = mount_point
488 self.fs_type = fs_type
489 self.device = device
490 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700491 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700492
493 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800494 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700495 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700496 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700497 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700498
Tao Baod1de6f32017-03-01 16:38:48 -0800499 assert fstab_version == 2
500
501 d = {}
502 for line in data.split("\n"):
503 line = line.strip()
504 if not line or line.startswith("#"):
505 continue
506
507 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
508 pieces = line.split()
509 if len(pieces) != 5:
510 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
511
512 # Ignore entries that are managed by vold.
513 options = pieces[4]
514 if "voldmanaged=" in options:
515 continue
516
517 # It's a good line, parse it.
518 length = 0
519 options = options.split(",")
520 for i in options:
521 if i.startswith("length="):
522 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800523 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800524 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700525 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800526
Tao Baod1de6f32017-03-01 16:38:48 -0800527 mount_flags = pieces[3]
528 # Honor the SELinux context if present.
529 context = None
530 for i in mount_flags.split(","):
531 if i.startswith("context="):
532 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800533
Tao Baod1de6f32017-03-01 16:38:48 -0800534 mount_point = pieces[1]
535 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
536 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800537
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700538 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700539 # system. Other areas assume system is always at "/system" so point /system
540 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700541 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800542 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700543 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700544 return d
545
546
Doug Zongker37974732010-09-16 17:44:38 -0700547def DumpInfoDict(d):
548 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700549 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700550
Dan Albert8b72aef2015-03-23 19:13:21 -0700551
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800552def AppendAVBSigningArgs(cmd, partition):
553 """Append signing arguments for avbtool."""
554 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
555 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
556 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
557 if key_path and algorithm:
558 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700559 avb_salt = OPTIONS.info_dict.get("avb_salt")
560 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700561 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700562 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800563
564
Tao Bao02a08592018-07-22 12:40:45 -0700565def GetAvbChainedPartitionArg(partition, info_dict, key=None):
566 """Constructs and returns the arg to build or verify a chained partition.
567
568 Args:
569 partition: The partition name.
570 info_dict: The info dict to look up the key info and rollback index
571 location.
572 key: The key to be used for building or verifying the partition. Defaults to
573 the key listed in info_dict.
574
575 Returns:
576 A string of form "partition:rollback_index_location:key" that can be used to
577 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700578 """
579 if key is None:
580 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao1ac886e2019-06-26 11:58:22 -0700581 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700582 rollback_index_location = info_dict[
583 "avb_" + partition + "_rollback_index_location"]
584 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
585
586
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700587def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800588 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700589 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700590
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700591 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800592 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
593 we are building a two-step special image (i.e. building a recovery image to
594 be loaded into /boot in two-step OTAs).
595
596 Return the image data, or None if sourcedir does not appear to contains files
597 for building the requested image.
598 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700599
600 def make_ramdisk():
601 ramdisk_img = tempfile.NamedTemporaryFile()
602
603 if os.access(fs_config_file, os.F_OK):
604 cmd = ["mkbootfs", "-f", fs_config_file,
605 os.path.join(sourcedir, "RAMDISK")]
606 else:
607 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
608 p1 = Run(cmd, stdout=subprocess.PIPE)
609 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
610
611 p2.wait()
612 p1.wait()
613 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
614 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
615
616 return ramdisk_img
617
618 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
619 return None
620
621 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700622 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700623
Doug Zongkerd5131602012-08-02 14:46:42 -0700624 if info_dict is None:
625 info_dict = OPTIONS.info_dict
626
Doug Zongkereef39442009-04-02 12:14:19 -0700627 img = tempfile.NamedTemporaryFile()
628
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700629 if has_ramdisk:
630 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700631
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800632 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
633 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
634
635 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700636
Benoit Fradina45a8682014-07-14 21:00:43 +0200637 fn = os.path.join(sourcedir, "second")
638 if os.access(fn, os.F_OK):
639 cmd.append("--second")
640 cmd.append(fn)
641
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800642 fn = os.path.join(sourcedir, "dtb")
643 if os.access(fn, os.F_OK):
644 cmd.append("--dtb")
645 cmd.append(fn)
646
Doug Zongker171f1cd2009-06-15 22:36:37 -0700647 fn = os.path.join(sourcedir, "cmdline")
648 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700649 cmd.append("--cmdline")
650 cmd.append(open(fn).read().rstrip("\n"))
651
652 fn = os.path.join(sourcedir, "base")
653 if os.access(fn, os.F_OK):
654 cmd.append("--base")
655 cmd.append(open(fn).read().rstrip("\n"))
656
Ying Wang4de6b5b2010-08-25 14:29:34 -0700657 fn = os.path.join(sourcedir, "pagesize")
658 if os.access(fn, os.F_OK):
659 cmd.append("--pagesize")
660 cmd.append(open(fn).read().rstrip("\n"))
661
Tao Bao76def242017-11-21 09:25:31 -0800662 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700663 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700664 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700665
Tao Bao76def242017-11-21 09:25:31 -0800666 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000667 if args and args.strip():
668 cmd.extend(shlex.split(args))
669
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700670 if has_ramdisk:
671 cmd.extend(["--ramdisk", ramdisk_img.name])
672
Tao Baod95e9fd2015-03-29 23:07:41 -0700673 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800674 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700675 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700676 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700677 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700678 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700679
Tao Baobf70c312017-07-11 17:27:55 -0700680 # "boot" or "recovery", without extension.
681 partition_name = os.path.basename(sourcedir).lower()
682
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800683 if partition_name == "recovery":
684 if info_dict.get("include_recovery_dtbo") == "true":
685 fn = os.path.join(sourcedir, "recovery_dtbo")
686 cmd.extend(["--recovery_dtbo", fn])
687 if info_dict.get("include_recovery_acpio") == "true":
688 fn = os.path.join(sourcedir, "recovery_acpio")
689 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700690
Tao Bao986ee862018-10-04 15:46:16 -0700691 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700692
Tao Bao76def242017-11-21 09:25:31 -0800693 if (info_dict.get("boot_signer") == "true" and
694 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800695 # Hard-code the path as "/boot" for two-step special recovery image (which
696 # will be loaded into /boot during the two-step OTA).
697 if two_step_image:
698 path = "/boot"
699 else:
Tao Baobf70c312017-07-11 17:27:55 -0700700 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700701 cmd = [OPTIONS.boot_signer_path]
702 cmd.extend(OPTIONS.boot_signer_args)
703 cmd.extend([path, img.name,
704 info_dict["verity_key"] + ".pk8",
705 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700706 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700707
Tao Baod95e9fd2015-03-29 23:07:41 -0700708 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800709 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700710 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700711 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800712 # We have switched from the prebuilt futility binary to using the tool
713 # (futility-host) built from the source. Override the setting in the old
714 # TF.zip.
715 futility = info_dict["futility"]
716 if futility.startswith("prebuilts/"):
717 futility = "futility-host"
718 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700719 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700720 info_dict["vboot_key"] + ".vbprivk",
721 info_dict["vboot_subkey"] + ".vbprivk",
722 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700723 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700724 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700725
Tao Baof3282b42015-04-01 11:21:55 -0700726 # Clean up the temp files.
727 img_unsigned.close()
728 img_keyblock.close()
729
David Zeuthen8fecb282017-12-01 16:24:01 -0500730 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800731 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700732 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500733 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400734 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700735 "--partition_size", str(part_size), "--partition_name",
736 partition_name]
737 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500738 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400739 if args and args.strip():
740 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700741 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500742
743 img.seek(os.SEEK_SET, 0)
744 data = img.read()
745
746 if has_ramdisk:
747 ramdisk_img.close()
748 img.close()
749
750 return data
751
752
Doug Zongkerd5131602012-08-02 14:46:42 -0700753def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800754 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700755 """Return a File object with the desired bootable image.
756
757 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
758 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
759 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700760
Doug Zongker55d93282011-01-25 17:03:34 -0800761 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
762 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700763 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800764 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700765
766 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
767 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700768 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700769 return File.FromLocalFile(name, prebuilt_path)
770
Tao Bao32fcdab2018-10-12 10:30:39 -0700771 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700772
773 if info_dict is None:
774 info_dict = OPTIONS.info_dict
775
776 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800777 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
778 # for recovery.
779 has_ramdisk = (info_dict.get("system_root_image") != "true" or
780 prebuilt_name != "boot.img" or
781 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700782
Doug Zongker6f1d0312014-08-22 08:07:12 -0700783 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400784 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
785 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800786 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700787 if data:
788 return File(name, data)
789 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800790
Doug Zongkereef39442009-04-02 12:14:19 -0700791
Narayan Kamatha07bf042017-08-14 14:49:21 +0100792def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800793 """Gunzips the given gzip compressed file to a given output file."""
794 with gzip.open(in_filename, "rb") as in_file, \
795 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100796 shutil.copyfileobj(in_file, out_file)
797
798
Tao Bao0ff15de2019-03-20 11:26:06 -0700799def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800800 """Unzips the archive to the given directory.
801
802 Args:
803 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800804 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700805 patterns: Files to unzip from the archive. If omitted, will unzip the entire
806 archvie. Non-matching patterns will be filtered out. If there's no match
807 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800808 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800809 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700810 if patterns is not None:
811 # Filter out non-matching patterns. unzip will complain otherwise.
812 with zipfile.ZipFile(filename) as input_zip:
813 names = input_zip.namelist()
814 filtered = [
815 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
816
817 # There isn't any matching files. Don't unzip anything.
818 if not filtered:
819 return
820 cmd.extend(filtered)
821
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800822 RunAndCheckOutput(cmd)
823
824
Doug Zongker75f17362009-12-08 13:46:44 -0800825def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800826 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800827
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800828 Args:
829 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
830 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
831
832 pattern: Files to unzip from the archive. If omitted, will unzip the entire
833 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800834
Tao Bao1c830bf2017-12-25 10:43:47 -0800835 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800836 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800837 """
Doug Zongkereef39442009-04-02 12:14:19 -0700838
Tao Bao1c830bf2017-12-25 10:43:47 -0800839 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800840 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
841 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800842 UnzipToDir(m.group(1), tmp, pattern)
843 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800844 filename = m.group(1)
845 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800846 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800847
Tao Baodba59ee2018-01-09 13:21:02 -0800848 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700849
850
Yifan Hong8a66a712019-04-04 15:37:57 -0700851def GetUserImage(which, tmpdir, input_zip,
852 info_dict=None,
853 allow_shared_blocks=None,
854 hashtree_info_generator=None,
855 reset_file_map=False):
856 """Returns an Image object suitable for passing to BlockImageDiff.
857
858 This function loads the specified image from the given path. If the specified
859 image is sparse, it also performs additional processing for OTA purpose. For
860 example, it always adds block 0 to clobbered blocks list. It also detects
861 files that cannot be reconstructed from the block list, for whom we should
862 avoid applying imgdiff.
863
864 Args:
865 which: The partition name.
866 tmpdir: The directory that contains the prebuilt image and block map file.
867 input_zip: The target-files ZIP archive.
868 info_dict: The dict to be looked up for relevant info.
869 allow_shared_blocks: If image is sparse, whether having shared blocks is
870 allowed. If none, it is looked up from info_dict.
871 hashtree_info_generator: If present and image is sparse, generates the
872 hashtree_info for this sparse image.
873 reset_file_map: If true and image is sparse, reset file map before returning
874 the image.
875 Returns:
876 A Image object. If it is a sparse image and reset_file_map is False, the
877 image will have file_map info loaded.
878 """
Tao Baoc1a1ec32019-06-18 16:29:37 -0700879 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -0700880 info_dict = LoadInfoDict(input_zip)
881
882 is_sparse = info_dict.get("extfs_sparse_flag")
883
884 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
885 # shared blocks (i.e. some blocks will show up in multiple files' block
886 # list). We can only allocate such shared blocks to the first "owner", and
887 # disable imgdiff for all later occurrences.
888 if allow_shared_blocks is None:
889 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
890
891 if is_sparse:
892 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
893 hashtree_info_generator)
894 if reset_file_map:
895 img.ResetFileMap()
896 return img
897 else:
898 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
899
900
901def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
902 """Returns a Image object suitable for passing to BlockImageDiff.
903
904 This function loads the specified non-sparse image from the given path.
905
906 Args:
907 which: The partition name.
908 tmpdir: The directory that contains the prebuilt image and block map file.
909 Returns:
910 A Image object.
911 """
912 path = os.path.join(tmpdir, "IMAGES", which + ".img")
913 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
914
915 # The image and map files must have been created prior to calling
916 # ota_from_target_files.py (since LMP).
917 assert os.path.exists(path) and os.path.exists(mappath)
918
919 return blockimgdiff.FileImage(path, hashtree_info_generator=
920 hashtree_info_generator)
921
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700922def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
923 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800924 """Returns a SparseImage object suitable for passing to BlockImageDiff.
925
926 This function loads the specified sparse image from the given path, and
927 performs additional processing for OTA purpose. For example, it always adds
928 block 0 to clobbered blocks list. It also detects files that cannot be
929 reconstructed from the block list, for whom we should avoid applying imgdiff.
930
931 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700932 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800933 tmpdir: The directory that contains the prebuilt image and block map file.
934 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800935 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700936 hashtree_info_generator: If present, generates the hashtree_info for this
937 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800938 Returns:
939 A SparseImage object, with file_map info loaded.
940 """
Tao Baoc765cca2018-01-31 17:32:40 -0800941 path = os.path.join(tmpdir, "IMAGES", which + ".img")
942 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
943
944 # The image and map files must have been created prior to calling
945 # ota_from_target_files.py (since LMP).
946 assert os.path.exists(path) and os.path.exists(mappath)
947
948 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
949 # it to clobbered_blocks so that it will be written to the target
950 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
951 clobbered_blocks = "0"
952
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700953 image = sparse_img.SparseImage(
954 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
955 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800956
957 # block.map may contain less blocks, because mke2fs may skip allocating blocks
958 # if they contain all zeros. We can't reconstruct such a file from its block
959 # list. Tag such entries accordingly. (Bug: 65213616)
960 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800961 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700962 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800963 continue
964
Tom Cherryd14b8952018-08-09 14:26:00 -0700965 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
966 # filename listed in system.map may contain an additional leading slash
967 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
968 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -0800969 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -0700970
Tom Cherryd14b8952018-08-09 14:26:00 -0700971 # Special handling another case, where files not under /system
972 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700973 if which == 'system' and not arcname.startswith('SYSTEM'):
974 arcname = 'ROOT/' + arcname
975
976 assert arcname in input_zip.namelist(), \
977 "Failed to find the ZIP entry for {}".format(entry)
978
Tao Baoc765cca2018-01-31 17:32:40 -0800979 info = input_zip.getinfo(arcname)
980 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800981
982 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800983 # image, check the original block list to determine its completeness. Note
984 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800985 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800986 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800987
Tao Baoc765cca2018-01-31 17:32:40 -0800988 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
989 ranges.extra['incomplete'] = True
990
991 return image
992
993
Doug Zongkereef39442009-04-02 12:14:19 -0700994def GetKeyPasswords(keylist):
995 """Given a list of keys, prompt the user to enter passwords for
996 those which require them. Return a {key: password} dict. password
997 will be None if the key has no password."""
998
Doug Zongker8ce7c252009-05-22 13:34:54 -0700999 no_passwords = []
1000 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001001 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001002 devnull = open("/dev/null", "w+b")
1003 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001004 # We don't need a password for things that aren't really keys.
1005 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001006 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001007 continue
1008
T.R. Fullhart37e10522013-03-18 10:31:26 -07001009 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001010 "-inform", "DER", "-nocrypt"],
1011 stdin=devnull.fileno(),
1012 stdout=devnull.fileno(),
1013 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001014 p.communicate()
1015 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001016 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001017 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001018 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001019 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1020 "-inform", "DER", "-passin", "pass:"],
1021 stdin=devnull.fileno(),
1022 stdout=devnull.fileno(),
1023 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001024 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001025 if p.returncode == 0:
1026 # Encrypted key with empty string as password.
1027 key_passwords[k] = ''
1028 elif stderr.startswith('Error decrypting key'):
1029 # Definitely encrypted key.
1030 # It would have said "Error reading key" if it didn't parse correctly.
1031 need_passwords.append(k)
1032 else:
1033 # Potentially, a type of key that openssl doesn't understand.
1034 # We'll let the routines in signapk.jar handle it.
1035 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001036 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001037
T.R. Fullhart37e10522013-03-18 10:31:26 -07001038 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001039 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001040 return key_passwords
1041
1042
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001043def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001044 """Gets the minSdkVersion declared in the APK.
1045
1046 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1047 This can be both a decimal number (API Level) or a codename.
1048
1049 Args:
1050 apk_name: The APK filename.
1051
1052 Returns:
1053 The parsed SDK version string.
1054
1055 Raises:
1056 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001057 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001058 proc = Run(
1059 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1060 stderr=subprocess.PIPE)
1061 stdoutdata, stderrdata = proc.communicate()
1062 if proc.returncode != 0:
1063 raise ExternalError(
1064 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1065 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001066
Tao Baof47bf0f2018-03-21 23:28:51 -07001067 for line in stdoutdata.split("\n"):
1068 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001069 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1070 if m:
1071 return m.group(1)
1072 raise ExternalError("No minSdkVersion returned by aapt")
1073
1074
1075def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001076 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001077
Tao Baof47bf0f2018-03-21 23:28:51 -07001078 If minSdkVersion is set to a codename, it is translated to a number using the
1079 provided map.
1080
1081 Args:
1082 apk_name: The APK filename.
1083
1084 Returns:
1085 The parsed SDK version number.
1086
1087 Raises:
1088 ExternalError: On failing to get the min SDK version number.
1089 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001090 version = GetMinSdkVersion(apk_name)
1091 try:
1092 return int(version)
1093 except ValueError:
1094 # Not a decimal number. Codename?
1095 if version in codename_to_api_level_map:
1096 return codename_to_api_level_map[version]
1097 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001098 raise ExternalError(
1099 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1100 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001101
1102
1103def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001104 codename_to_api_level_map=None, whole_file=False,
1105 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001106 """Sign the input_name zip/jar/apk, producing output_name. Use the
1107 given key and password (the latter may be None if the key does not
1108 have a password.
1109
Doug Zongker951495f2009-08-14 12:44:19 -07001110 If whole_file is true, use the "-w" option to SignApk to embed a
1111 signature that covers the whole file in the archive comment of the
1112 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001113
1114 min_api_level is the API Level (int) of the oldest platform this file may end
1115 up on. If not specified for an APK, the API Level is obtained by interpreting
1116 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1117
1118 codename_to_api_level_map is needed to translate the codename which may be
1119 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001120
1121 Caller may optionally specify extra args to be passed to SignApk, which
1122 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001123 """
Tao Bao76def242017-11-21 09:25:31 -08001124 if codename_to_api_level_map is None:
1125 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001126 if extra_signapk_args is None:
1127 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001128
Alex Klyubin9667b182015-12-10 13:38:50 -08001129 java_library_path = os.path.join(
1130 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1131
Tao Baoe95540e2016-11-08 12:08:53 -08001132 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1133 ["-Djava.library.path=" + java_library_path,
1134 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001135 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001136 if whole_file:
1137 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001138
1139 min_sdk_version = min_api_level
1140 if min_sdk_version is None:
1141 if not whole_file:
1142 min_sdk_version = GetMinSdkVersionInt(
1143 input_name, codename_to_api_level_map)
1144 if min_sdk_version is not None:
1145 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1146
T.R. Fullhart37e10522013-03-18 10:31:26 -07001147 cmd.extend([key + OPTIONS.public_key_suffix,
1148 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001149 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001150
Tao Bao73dd4f42018-10-04 16:25:33 -07001151 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001152 if password is not None:
1153 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001154 stdoutdata, _ = proc.communicate(password)
1155 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001156 raise ExternalError(
1157 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001158 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001159
Doug Zongkereef39442009-04-02 12:14:19 -07001160
Doug Zongker37974732010-09-16 17:44:38 -07001161def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001162 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001163
Tao Bao9dd909e2017-11-14 11:27:32 -08001164 For non-AVB images, raise exception if the data is too big. Print a warning
1165 if the data is nearing the maximum size.
1166
1167 For AVB images, the actual image size should be identical to the limit.
1168
1169 Args:
1170 data: A string that contains all the data for the partition.
1171 target: The partition name. The ".img" suffix is optional.
1172 info_dict: The dict to be looked up for relevant info.
1173 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001174 if target.endswith(".img"):
1175 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001176 mount_point = "/" + target
1177
Ying Wangf8824af2014-06-03 14:07:27 -07001178 fs_type = None
1179 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001180 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001181 if mount_point == "/userdata":
1182 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001183 p = info_dict["fstab"][mount_point]
1184 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001185 device = p.device
1186 if "/" in device:
1187 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001188 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001189 if not fs_type or not limit:
1190 return
Doug Zongkereef39442009-04-02 12:14:19 -07001191
Andrew Boie0f9aec82012-02-14 09:32:52 -08001192 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001193 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1194 # path.
1195 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1196 if size != limit:
1197 raise ExternalError(
1198 "Mismatching image size for %s: expected %d actual %d" % (
1199 target, limit, size))
1200 else:
1201 pct = float(size) * 100.0 / limit
1202 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1203 if pct >= 99.0:
1204 raise ExternalError(msg)
1205 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001206 logger.warning("\n WARNING: %s\n", msg)
1207 else:
1208 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001209
1210
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001211def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001212 """Parses the APK certs info from a given target-files zip.
1213
1214 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1215 tuple with the following elements: (1) a dictionary that maps packages to
1216 certs (based on the "certificate" and "private_key" attributes in the file;
1217 (2) a string representing the extension of compressed APKs in the target files
1218 (e.g ".gz", ".bro").
1219
1220 Args:
1221 tf_zip: The input target_files ZipFile (already open).
1222
1223 Returns:
1224 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1225 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1226 no compressed APKs.
1227 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001228 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001229 compressed_extension = None
1230
Tao Bao0f990332017-09-08 19:02:54 -07001231 # META/apkcerts.txt contains the info for _all_ the packages known at build
1232 # time. Filter out the ones that are not installed.
1233 installed_files = set()
1234 for name in tf_zip.namelist():
1235 basename = os.path.basename(name)
1236 if basename:
1237 installed_files.add(basename)
1238
Tao Baoda30cfa2017-12-01 16:19:46 -08001239 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001240 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001241 if not line:
1242 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001243 m = re.match(
1244 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1245 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1246 line)
1247 if not m:
1248 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001249
Tao Bao818ddf52018-01-05 11:17:34 -08001250 matches = m.groupdict()
1251 cert = matches["CERT"]
1252 privkey = matches["PRIVKEY"]
1253 name = matches["NAME"]
1254 this_compressed_extension = matches["COMPRESSED"]
1255
1256 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1257 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1258 if cert in SPECIAL_CERT_STRINGS and not privkey:
1259 certmap[name] = cert
1260 elif (cert.endswith(OPTIONS.public_key_suffix) and
1261 privkey.endswith(OPTIONS.private_key_suffix) and
1262 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1263 certmap[name] = cert[:-public_key_suffix_len]
1264 else:
1265 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1266
1267 if not this_compressed_extension:
1268 continue
1269
1270 # Only count the installed files.
1271 filename = name + '.' + this_compressed_extension
1272 if filename not in installed_files:
1273 continue
1274
1275 # Make sure that all the values in the compression map have the same
1276 # extension. We don't support multiple compression methods in the same
1277 # system image.
1278 if compressed_extension:
1279 if this_compressed_extension != compressed_extension:
1280 raise ValueError(
1281 "Multiple compressed extensions: {} vs {}".format(
1282 compressed_extension, this_compressed_extension))
1283 else:
1284 compressed_extension = this_compressed_extension
1285
1286 return (certmap,
1287 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001288
1289
Doug Zongkereef39442009-04-02 12:14:19 -07001290COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001291Global options
1292
1293 -p (--path) <dir>
1294 Prepend <dir>/bin to the list of places to search for binaries run by this
1295 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001296
Doug Zongker05d3dea2009-06-22 11:32:31 -07001297 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001298 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001299
Tao Bao30df8b42018-04-23 15:32:53 -07001300 -x (--extra) <key=value>
1301 Add a key/value pair to the 'extras' dict, which device-specific extension
1302 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001303
Doug Zongkereef39442009-04-02 12:14:19 -07001304 -v (--verbose)
1305 Show command lines being executed.
1306
1307 -h (--help)
1308 Display this usage message and exit.
1309"""
1310
1311def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001312 print(docstring.rstrip("\n"))
1313 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001314
1315
1316def ParseOptions(argv,
1317 docstring,
1318 extra_opts="", extra_long_opts=(),
1319 extra_option_handler=None):
1320 """Parse the options in argv and return any arguments that aren't
1321 flags. docstring is the calling module's docstring, to be displayed
1322 for errors and -h. extra_opts and extra_long_opts are for flags
1323 defined by the caller, which are processed by passing them to
1324 extra_option_handler."""
1325
1326 try:
1327 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001328 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001329 ["help", "verbose", "path=", "signapk_path=",
1330 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001331 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001332 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1333 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001334 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001335 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001336 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001337 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001338 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001339 sys.exit(2)
1340
Doug Zongkereef39442009-04-02 12:14:19 -07001341 for o, a in opts:
1342 if o in ("-h", "--help"):
1343 Usage(docstring)
1344 sys.exit()
1345 elif o in ("-v", "--verbose"):
1346 OPTIONS.verbose = True
1347 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001348 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001349 elif o in ("--signapk_path",):
1350 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001351 elif o in ("--signapk_shared_library_path",):
1352 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001353 elif o in ("--extra_signapk_args",):
1354 OPTIONS.extra_signapk_args = shlex.split(a)
1355 elif o in ("--java_path",):
1356 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001357 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001358 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001359 elif o in ("--public_key_suffix",):
1360 OPTIONS.public_key_suffix = a
1361 elif o in ("--private_key_suffix",):
1362 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001363 elif o in ("--boot_signer_path",):
1364 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001365 elif o in ("--boot_signer_args",):
1366 OPTIONS.boot_signer_args = shlex.split(a)
1367 elif o in ("--verity_signer_path",):
1368 OPTIONS.verity_signer_path = a
1369 elif o in ("--verity_signer_args",):
1370 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001371 elif o in ("-s", "--device_specific"):
1372 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001373 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001374 key, value = a.split("=", 1)
1375 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001376 else:
1377 if extra_option_handler is None or not extra_option_handler(o, a):
1378 assert False, "unknown option \"%s\"" % (o,)
1379
Doug Zongker85448772014-09-09 14:59:20 -07001380 if OPTIONS.search_path:
1381 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1382 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001383
1384 return args
1385
1386
Tao Bao4c851b12016-09-19 13:54:38 -07001387def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001388 """Make a temp file and add it to the list of things to be deleted
1389 when Cleanup() is called. Return the filename."""
1390 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1391 os.close(fd)
1392 OPTIONS.tempfiles.append(fn)
1393 return fn
1394
1395
Tao Bao1c830bf2017-12-25 10:43:47 -08001396def MakeTempDir(prefix='tmp', suffix=''):
1397 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1398
1399 Returns:
1400 The absolute pathname of the new directory.
1401 """
1402 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1403 OPTIONS.tempfiles.append(dir_name)
1404 return dir_name
1405
1406
Doug Zongkereef39442009-04-02 12:14:19 -07001407def Cleanup():
1408 for i in OPTIONS.tempfiles:
1409 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001410 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001411 else:
1412 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001413 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001414
1415
1416class PasswordManager(object):
1417 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001418 self.editor = os.getenv("EDITOR")
1419 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001420
1421 def GetPasswords(self, items):
1422 """Get passwords corresponding to each string in 'items',
1423 returning a dict. (The dict may have keys in addition to the
1424 values in 'items'.)
1425
1426 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1427 user edit that file to add more needed passwords. If no editor is
1428 available, or $ANDROID_PW_FILE isn't define, prompts the user
1429 interactively in the ordinary way.
1430 """
1431
1432 current = self.ReadFile()
1433
1434 first = True
1435 while True:
1436 missing = []
1437 for i in items:
1438 if i not in current or not current[i]:
1439 missing.append(i)
1440 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001441 if not missing:
1442 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001443
1444 for i in missing:
1445 current[i] = ""
1446
1447 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001448 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001449 if sys.version_info[0] >= 3:
1450 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001451 answer = raw_input("try to edit again? [y]> ").strip()
1452 if answer and answer[0] not in 'yY':
1453 raise RuntimeError("key passwords unavailable")
1454 first = False
1455
1456 current = self.UpdateAndReadFile(current)
1457
Dan Albert8b72aef2015-03-23 19:13:21 -07001458 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001459 """Prompt the user to enter a value (password) for each key in
1460 'current' whose value is fales. Returns a new dict with all the
1461 values.
1462 """
1463 result = {}
1464 for k, v in sorted(current.iteritems()):
1465 if v:
1466 result[k] = v
1467 else:
1468 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001469 result[k] = getpass.getpass(
1470 "Enter password for %s key> " % k).strip()
1471 if result[k]:
1472 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001473 return result
1474
1475 def UpdateAndReadFile(self, current):
1476 if not self.editor or not self.pwfile:
1477 return self.PromptResult(current)
1478
1479 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001480 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001481 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1482 f.write("# (Additional spaces are harmless.)\n\n")
1483
1484 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001485 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1486 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001487 f.write("[[[ %s ]]] %s\n" % (v, k))
1488 if not v and first_line is None:
1489 # position cursor on first line with no password.
1490 first_line = i + 4
1491 f.close()
1492
Tao Bao986ee862018-10-04 15:46:16 -07001493 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001494
1495 return self.ReadFile()
1496
1497 def ReadFile(self):
1498 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001499 if self.pwfile is None:
1500 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001501 try:
1502 f = open(self.pwfile, "r")
1503 for line in f:
1504 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001505 if not line or line[0] == '#':
1506 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001507 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1508 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001509 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001510 else:
1511 result[m.group(2)] = m.group(1)
1512 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001513 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001514 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001515 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001516 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001517
1518
Dan Albert8e0178d2015-01-27 15:53:15 -08001519def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1520 compress_type=None):
1521 import datetime
1522
1523 # http://b/18015246
1524 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1525 # for files larger than 2GiB. We can work around this by adjusting their
1526 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1527 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1528 # it isn't clear to me exactly what circumstances cause this).
1529 # `zipfile.write()` must be used directly to work around this.
1530 #
1531 # This mess can be avoided if we port to python3.
1532 saved_zip64_limit = zipfile.ZIP64_LIMIT
1533 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1534
1535 if compress_type is None:
1536 compress_type = zip_file.compression
1537 if arcname is None:
1538 arcname = filename
1539
1540 saved_stat = os.stat(filename)
1541
1542 try:
1543 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1544 # file to be zipped and reset it when we're done.
1545 os.chmod(filename, perms)
1546
1547 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001548 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1549 # intentional. zip stores datetimes in local time without a time zone
1550 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1551 # in the zip archive.
1552 local_epoch = datetime.datetime.fromtimestamp(0)
1553 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001554 os.utime(filename, (timestamp, timestamp))
1555
1556 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1557 finally:
1558 os.chmod(filename, saved_stat.st_mode)
1559 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1560 zipfile.ZIP64_LIMIT = saved_zip64_limit
1561
1562
Tao Bao58c1b962015-05-20 09:32:18 -07001563def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001564 compress_type=None):
1565 """Wrap zipfile.writestr() function to work around the zip64 limit.
1566
1567 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1568 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1569 when calling crc32(bytes).
1570
1571 But it still works fine to write a shorter string into a large zip file.
1572 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1573 when we know the string won't be too long.
1574 """
1575
1576 saved_zip64_limit = zipfile.ZIP64_LIMIT
1577 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1578
1579 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1580 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001581 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001582 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001583 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001584 else:
Tao Baof3282b42015-04-01 11:21:55 -07001585 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001586 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1587 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1588 # such a case (since
1589 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1590 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1591 # permission bits. We follow the logic in Python 3 to get consistent
1592 # behavior between using the two versions.
1593 if not zinfo.external_attr:
1594 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001595
1596 # If compress_type is given, it overrides the value in zinfo.
1597 if compress_type is not None:
1598 zinfo.compress_type = compress_type
1599
Tao Bao58c1b962015-05-20 09:32:18 -07001600 # If perms is given, it has a priority.
1601 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001602 # If perms doesn't set the file type, mark it as a regular file.
1603 if perms & 0o770000 == 0:
1604 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001605 zinfo.external_attr = perms << 16
1606
Tao Baof3282b42015-04-01 11:21:55 -07001607 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001608 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1609
Dan Albert8b72aef2015-03-23 19:13:21 -07001610 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001611 zipfile.ZIP64_LIMIT = saved_zip64_limit
1612
1613
Tao Bao89d7ab22017-12-14 17:05:33 -08001614def ZipDelete(zip_filename, entries):
1615 """Deletes entries from a ZIP file.
1616
1617 Since deleting entries from a ZIP file is not supported, it shells out to
1618 'zip -d'.
1619
1620 Args:
1621 zip_filename: The name of the ZIP file.
1622 entries: The name of the entry, or the list of names to be deleted.
1623
1624 Raises:
1625 AssertionError: In case of non-zero return from 'zip'.
1626 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001627 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001628 entries = [entries]
1629 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001630 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001631
1632
Tao Baof3282b42015-04-01 11:21:55 -07001633def ZipClose(zip_file):
1634 # http://b/18015246
1635 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1636 # central directory.
1637 saved_zip64_limit = zipfile.ZIP64_LIMIT
1638 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1639
1640 zip_file.close()
1641
1642 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001643
1644
1645class DeviceSpecificParams(object):
1646 module = None
1647 def __init__(self, **kwargs):
1648 """Keyword arguments to the constructor become attributes of this
1649 object, which is passed to all functions in the device-specific
1650 module."""
1651 for k, v in kwargs.iteritems():
1652 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001653 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001654
1655 if self.module is None:
1656 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001657 if not path:
1658 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001659 try:
1660 if os.path.isdir(path):
1661 info = imp.find_module("releasetools", [path])
1662 else:
1663 d, f = os.path.split(path)
1664 b, x = os.path.splitext(f)
1665 if x == ".py":
1666 f = b
1667 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001668 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001669 self.module = imp.load_module("device_specific", *info)
1670 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001671 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001672
1673 def _DoCall(self, function_name, *args, **kwargs):
1674 """Call the named function in the device-specific module, passing
1675 the given args and kwargs. The first argument to the call will be
1676 the DeviceSpecific object itself. If there is no module, or the
1677 module does not define the function, return the value of the
1678 'default' kwarg (which itself defaults to None)."""
1679 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001680 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001681 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1682
1683 def FullOTA_Assertions(self):
1684 """Called after emitting the block of assertions at the top of a
1685 full OTA package. Implementations can add whatever additional
1686 assertions they like."""
1687 return self._DoCall("FullOTA_Assertions")
1688
Doug Zongkere5ff5902012-01-17 10:55:37 -08001689 def FullOTA_InstallBegin(self):
1690 """Called at the start of full OTA installation."""
1691 return self._DoCall("FullOTA_InstallBegin")
1692
Yifan Hong10c530d2018-12-27 17:34:18 -08001693 def FullOTA_GetBlockDifferences(self):
1694 """Called during full OTA installation and verification.
1695 Implementation should return a list of BlockDifference objects describing
1696 the update on each additional partitions.
1697 """
1698 return self._DoCall("FullOTA_GetBlockDifferences")
1699
Doug Zongker05d3dea2009-06-22 11:32:31 -07001700 def FullOTA_InstallEnd(self):
1701 """Called at the end of full OTA installation; typically this is
1702 used to install the image for the device's baseband processor."""
1703 return self._DoCall("FullOTA_InstallEnd")
1704
1705 def IncrementalOTA_Assertions(self):
1706 """Called after emitting the block of assertions at the top of an
1707 incremental OTA package. Implementations can add whatever
1708 additional assertions they like."""
1709 return self._DoCall("IncrementalOTA_Assertions")
1710
Doug Zongkere5ff5902012-01-17 10:55:37 -08001711 def IncrementalOTA_VerifyBegin(self):
1712 """Called at the start of the verification phase of incremental
1713 OTA installation; additional checks can be placed here to abort
1714 the script before any changes are made."""
1715 return self._DoCall("IncrementalOTA_VerifyBegin")
1716
Doug Zongker05d3dea2009-06-22 11:32:31 -07001717 def IncrementalOTA_VerifyEnd(self):
1718 """Called at the end of the verification phase of incremental OTA
1719 installation; additional checks can be placed here to abort the
1720 script before any changes are made."""
1721 return self._DoCall("IncrementalOTA_VerifyEnd")
1722
Doug Zongkere5ff5902012-01-17 10:55:37 -08001723 def IncrementalOTA_InstallBegin(self):
1724 """Called at the start of incremental OTA installation (after
1725 verification is complete)."""
1726 return self._DoCall("IncrementalOTA_InstallBegin")
1727
Yifan Hong10c530d2018-12-27 17:34:18 -08001728 def IncrementalOTA_GetBlockDifferences(self):
1729 """Called during incremental OTA installation and verification.
1730 Implementation should return a list of BlockDifference objects describing
1731 the update on each additional partitions.
1732 """
1733 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1734
Doug Zongker05d3dea2009-06-22 11:32:31 -07001735 def IncrementalOTA_InstallEnd(self):
1736 """Called at the end of incremental OTA installation; typically
1737 this is used to install the image for the device's baseband
1738 processor."""
1739 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001740
Tao Bao9bc6bb22015-11-09 16:58:28 -08001741 def VerifyOTA_Assertions(self):
1742 return self._DoCall("VerifyOTA_Assertions")
1743
Tao Bao76def242017-11-21 09:25:31 -08001744
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001745class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001746 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001747 self.name = name
1748 self.data = data
1749 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001750 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001751 self.sha1 = sha1(data).hexdigest()
1752
1753 @classmethod
1754 def FromLocalFile(cls, name, diskname):
1755 f = open(diskname, "rb")
1756 data = f.read()
1757 f.close()
1758 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001759
1760 def WriteToTemp(self):
1761 t = tempfile.NamedTemporaryFile()
1762 t.write(self.data)
1763 t.flush()
1764 return t
1765
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001766 def WriteToDir(self, d):
1767 with open(os.path.join(d, self.name), "wb") as fp:
1768 fp.write(self.data)
1769
Geremy Condra36bd3652014-02-06 19:45:10 -08001770 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001771 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001772
Tao Bao76def242017-11-21 09:25:31 -08001773
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001774DIFF_PROGRAM_BY_EXT = {
1775 ".gz" : "imgdiff",
1776 ".zip" : ["imgdiff", "-z"],
1777 ".jar" : ["imgdiff", "-z"],
1778 ".apk" : ["imgdiff", "-z"],
1779 ".img" : "imgdiff",
1780 }
1781
Tao Bao76def242017-11-21 09:25:31 -08001782
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001783class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001784 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001785 self.tf = tf
1786 self.sf = sf
1787 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001788 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001789
1790 def ComputePatch(self):
1791 """Compute the patch (as a string of data) needed to turn sf into
1792 tf. Returns the same tuple as GetPatch()."""
1793
1794 tf = self.tf
1795 sf = self.sf
1796
Doug Zongker24cd2802012-08-14 16:36:15 -07001797 if self.diff_program:
1798 diff_program = self.diff_program
1799 else:
1800 ext = os.path.splitext(tf.name)[1]
1801 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001802
1803 ttemp = tf.WriteToTemp()
1804 stemp = sf.WriteToTemp()
1805
1806 ext = os.path.splitext(tf.name)[1]
1807
1808 try:
1809 ptemp = tempfile.NamedTemporaryFile()
1810 if isinstance(diff_program, list):
1811 cmd = copy.copy(diff_program)
1812 else:
1813 cmd = [diff_program]
1814 cmd.append(stemp.name)
1815 cmd.append(ttemp.name)
1816 cmd.append(ptemp.name)
1817 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001818 err = []
1819 def run():
1820 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001821 if e:
1822 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001823 th = threading.Thread(target=run)
1824 th.start()
1825 th.join(timeout=300) # 5 mins
1826 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001827 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001828 p.terminate()
1829 th.join(5)
1830 if th.is_alive():
1831 p.kill()
1832 th.join()
1833
Tianjie Xua2a9f992018-01-05 15:15:54 -08001834 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001835 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001836 self.patch = None
1837 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001838 diff = ptemp.read()
1839 finally:
1840 ptemp.close()
1841 stemp.close()
1842 ttemp.close()
1843
1844 self.patch = diff
1845 return self.tf, self.sf, self.patch
1846
1847
1848 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001849 """Returns a tuple of (target_file, source_file, patch_data).
1850
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001851 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001852 computing the patch failed.
1853 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001854 return self.tf, self.sf, self.patch
1855
1856
1857def ComputeDifferences(diffs):
1858 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001859 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001860
1861 # Do the largest files first, to try and reduce the long-pole effect.
1862 by_size = [(i.tf.size, i) for i in diffs]
1863 by_size.sort(reverse=True)
1864 by_size = [i[1] for i in by_size]
1865
1866 lock = threading.Lock()
1867 diff_iter = iter(by_size) # accessed under lock
1868
1869 def worker():
1870 try:
1871 lock.acquire()
1872 for d in diff_iter:
1873 lock.release()
1874 start = time.time()
1875 d.ComputePatch()
1876 dur = time.time() - start
1877 lock.acquire()
1878
1879 tf, sf, patch = d.GetPatch()
1880 if sf.name == tf.name:
1881 name = tf.name
1882 else:
1883 name = "%s (%s)" % (tf.name, sf.name)
1884 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001885 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001886 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001887 logger.info(
1888 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1889 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001890 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001891 except Exception:
1892 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001893 raise
1894
1895 # start worker threads; wait for them all to finish.
1896 threads = [threading.Thread(target=worker)
1897 for i in range(OPTIONS.worker_threads)]
1898 for th in threads:
1899 th.start()
1900 while threads:
1901 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001902
1903
Dan Albert8b72aef2015-03-23 19:13:21 -07001904class BlockDifference(object):
1905 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001906 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001907 self.tgt = tgt
1908 self.src = src
1909 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001910 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001911 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001912
Tao Baodd2a5892015-03-12 12:32:37 -07001913 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001914 version = max(
1915 int(i) for i in
1916 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001917 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001918 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001919
1920 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001921 version=self.version,
1922 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001923 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001924 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001925 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001926 self.touched_src_ranges = b.touched_src_ranges
1927 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001928
Yifan Hong10c530d2018-12-27 17:34:18 -08001929 # On devices with dynamic partitions, for new partitions,
1930 # src is None but OPTIONS.source_info_dict is not.
1931 if OPTIONS.source_info_dict is None:
1932 is_dynamic_build = OPTIONS.info_dict.get(
1933 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001934 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001935 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001936 is_dynamic_build = OPTIONS.source_info_dict.get(
1937 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001938 is_dynamic_source = partition in shlex.split(
1939 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001940
Yifan Hongbb2658d2019-01-25 12:30:58 -08001941 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001942 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1943
Yifan Hongbb2658d2019-01-25 12:30:58 -08001944 # For dynamic partitions builds, check partition list in both source
1945 # and target build because new partitions may be added, and existing
1946 # partitions may be removed.
1947 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1948
Yifan Hong10c530d2018-12-27 17:34:18 -08001949 if is_dynamic:
1950 self.device = 'map_partition("%s")' % partition
1951 else:
1952 if OPTIONS.source_info_dict is None:
1953 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1954 else:
1955 _, device_path = GetTypeAndDevice("/" + partition,
1956 OPTIONS.source_info_dict)
1957 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001958
Tao Baod8d14be2016-02-04 14:26:02 -08001959 @property
1960 def required_cache(self):
1961 return self._required_cache
1962
Tao Bao76def242017-11-21 09:25:31 -08001963 def WriteScript(self, script, output_zip, progress=None,
1964 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001965 if not self.src:
1966 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001967 script.Print("Patching %s image unconditionally..." % (self.partition,))
1968 else:
1969 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001970
Dan Albert8b72aef2015-03-23 19:13:21 -07001971 if progress:
1972 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001973 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001974
1975 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001976 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001977
Tao Bao9bc6bb22015-11-09 16:58:28 -08001978 def WriteStrictVerifyScript(self, script):
1979 """Verify all the blocks in the care_map, including clobbered blocks.
1980
1981 This differs from the WriteVerifyScript() function: a) it prints different
1982 error messages; b) it doesn't allow half-way updated images to pass the
1983 verification."""
1984
1985 partition = self.partition
1986 script.Print("Verifying %s..." % (partition,))
1987 ranges = self.tgt.care_map
1988 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001989 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001990 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1991 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001992 self.device, ranges_str,
1993 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001994 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001995 script.AppendExtra("")
1996
Tao Baod522bdc2016-04-12 15:53:16 -07001997 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001998 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001999
2000 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002001 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002002 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002003
2004 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002005 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002006 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002007 ranges = self.touched_src_ranges
2008 expected_sha1 = self.touched_src_sha1
2009 else:
2010 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2011 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002012
2013 # No blocks to be checked, skipping.
2014 if not ranges:
2015 return
2016
Tao Bao5ece99d2015-05-12 11:42:31 -07002017 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002018 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002019 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002020 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2021 '"%s.patch.dat")) then' % (
2022 self.device, ranges_str, expected_sha1,
2023 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002024 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002025 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002026
Tianjie Xufc3422a2015-12-15 11:53:59 -08002027 if self.version >= 4:
2028
2029 # Bug: 21124327
2030 # When generating incrementals for the system and vendor partitions in
2031 # version 4 or newer, explicitly check the first block (which contains
2032 # the superblock) of the partition to see if it's what we expect. If
2033 # this check fails, give an explicit log message about the partition
2034 # having been remounted R/W (the most likely explanation).
2035 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002036 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002037
2038 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002039 if partition == "system":
2040 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2041 else:
2042 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002043 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002044 'ifelse (block_image_recover({device}, "{ranges}") && '
2045 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002046 'package_extract_file("{partition}.transfer.list"), '
2047 '"{partition}.new.dat", "{partition}.patch.dat"), '
2048 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002049 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002050 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002051 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002052
Tao Baodd2a5892015-03-12 12:32:37 -07002053 # Abort the OTA update. Note that the incremental OTA cannot be applied
2054 # even if it may match the checksum of the target partition.
2055 # a) If version < 3, operations like move and erase will make changes
2056 # unconditionally and damage the partition.
2057 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002058 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002059 if partition == "system":
2060 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2061 else:
2062 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2063 script.AppendExtra((
2064 'abort("E%d: %s partition has unexpected contents");\n'
2065 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002066
Yifan Hong10c530d2018-12-27 17:34:18 -08002067 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002068 partition = self.partition
2069 script.Print('Verifying the updated %s image...' % (partition,))
2070 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2071 ranges = self.tgt.care_map
2072 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002073 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002074 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002075 self.device, ranges_str,
2076 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002077
2078 # Bug: 20881595
2079 # Verify that extended blocks are really zeroed out.
2080 if self.tgt.extended:
2081 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002082 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002083 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002084 self.device, ranges_str,
2085 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002086 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002087 if partition == "system":
2088 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2089 else:
2090 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002091 script.AppendExtra(
2092 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002093 ' abort("E%d: %s partition has unexpected non-zero contents after '
2094 'OTA update");\n'
2095 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002096 else:
2097 script.Print('Verified the updated %s image.' % (partition,))
2098
Tianjie Xu209db462016-05-24 17:34:52 -07002099 if partition == "system":
2100 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2101 else:
2102 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2103
Tao Bao5fcaaef2015-06-01 13:40:49 -07002104 script.AppendExtra(
2105 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002106 ' abort("E%d: %s partition has unexpected contents after OTA '
2107 'update");\n'
2108 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002109
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002110 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002111 ZipWrite(output_zip,
2112 '{}.transfer.list'.format(self.path),
2113 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002114
Tao Bao76def242017-11-21 09:25:31 -08002115 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2116 # its size. Quailty 9 almost triples the compression time but doesn't
2117 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002118 # zip | brotli(quality 6) | brotli(quality 9)
2119 # compressed_size: 942M | 869M (~8% reduced) | 854M
2120 # compression_time: 75s | 265s | 719s
2121 # decompression_time: 15s | 25s | 25s
2122
2123 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002124 brotli_cmd = ['brotli', '--quality=6',
2125 '--output={}.new.dat.br'.format(self.path),
2126 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002127 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002128 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002129
2130 new_data_name = '{}.new.dat.br'.format(self.partition)
2131 ZipWrite(output_zip,
2132 '{}.new.dat.br'.format(self.path),
2133 new_data_name,
2134 compress_type=zipfile.ZIP_STORED)
2135 else:
2136 new_data_name = '{}.new.dat'.format(self.partition)
2137 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2138
Dan Albert8e0178d2015-01-27 15:53:15 -08002139 ZipWrite(output_zip,
2140 '{}.patch.dat'.format(self.path),
2141 '{}.patch.dat'.format(self.partition),
2142 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002143
Tianjie Xu209db462016-05-24 17:34:52 -07002144 if self.partition == "system":
2145 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2146 else:
2147 code = ErrorCode.VENDOR_UPDATE_FAILURE
2148
Yifan Hong10c530d2018-12-27 17:34:18 -08002149 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002150 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002151 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002152 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002153 device=self.device, partition=self.partition,
2154 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002155 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002156
Dan Albert8b72aef2015-03-23 19:13:21 -07002157 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002158 data = source.ReadRangeSet(ranges)
2159 ctx = sha1()
2160
2161 for p in data:
2162 ctx.update(p)
2163
2164 return ctx.hexdigest()
2165
Tao Baoe9b61912015-07-09 17:37:49 -07002166 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2167 """Return the hash value for all zero blocks."""
2168 zero_block = '\x00' * 4096
2169 ctx = sha1()
2170 for _ in range(num_blocks):
2171 ctx.update(zero_block)
2172
2173 return ctx.hexdigest()
2174
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002175
2176DataImage = blockimgdiff.DataImage
Yifan Hong8a66a712019-04-04 15:37:57 -07002177EmptyImage = blockimgdiff.EmptyImage
Tao Bao76def242017-11-21 09:25:31 -08002178
Doug Zongker96a57e72010-09-26 14:57:41 -07002179# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002180PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002181 "ext4": "EMMC",
2182 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002183 "f2fs": "EMMC",
2184 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002185}
Doug Zongker96a57e72010-09-26 14:57:41 -07002186
Tao Bao76def242017-11-21 09:25:31 -08002187
Doug Zongker96a57e72010-09-26 14:57:41 -07002188def GetTypeAndDevice(mount_point, info):
2189 fstab = info["fstab"]
2190 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002191 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2192 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002193 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002194 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002195
2196
2197def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002198 """Parses and converts a PEM-encoded certificate into DER-encoded.
2199
2200 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2201
2202 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002203 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002204 """
2205 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002206 save = False
2207 for line in data.split("\n"):
2208 if "--END CERTIFICATE--" in line:
2209 break
2210 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002211 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002212 if "--BEGIN CERTIFICATE--" in line:
2213 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002214 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002215 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002216
Tao Bao04e1f012018-02-04 12:13:35 -08002217
2218def ExtractPublicKey(cert):
2219 """Extracts the public key (PEM-encoded) from the given certificate file.
2220
2221 Args:
2222 cert: The certificate filename.
2223
2224 Returns:
2225 The public key string.
2226
2227 Raises:
2228 AssertionError: On non-zero return from 'openssl'.
2229 """
2230 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2231 # While openssl 1.1 writes the key into the given filename followed by '-out',
2232 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2233 # stdout instead.
2234 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2235 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2236 pubkey, stderrdata = proc.communicate()
2237 assert proc.returncode == 0, \
2238 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2239 return pubkey
2240
2241
Tao Bao1ac886e2019-06-26 11:58:22 -07002242def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002243 """Extracts the AVB public key from the given public or private key.
2244
2245 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002246 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002247 key: The input key file, which should be PEM-encoded public or private key.
2248
2249 Returns:
2250 The path to the extracted AVB public key file.
2251 """
2252 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2253 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002254 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002255 return output
2256
2257
Doug Zongker412c02f2014-02-13 10:58:24 -08002258def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2259 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002260 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002261
Tao Bao6d5d6232018-03-09 17:04:42 -08002262 Most of the space in the boot and recovery images is just the kernel, which is
2263 identical for the two, so the resulting patch should be efficient. Add it to
2264 the output zip, along with a shell script that is run from init.rc on first
2265 boot to actually do the patching and install the new recovery image.
2266
2267 Args:
2268 input_dir: The top-level input directory of the target-files.zip.
2269 output_sink: The callback function that writes the result.
2270 recovery_img: File object for the recovery image.
2271 boot_img: File objects for the boot image.
2272 info_dict: A dict returned by common.LoadInfoDict() on the input
2273 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002274 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002275 if info_dict is None:
2276 info_dict = OPTIONS.info_dict
2277
Tao Bao6d5d6232018-03-09 17:04:42 -08002278 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002279
Tao Baof2cffbd2015-07-22 12:33:18 -07002280 if full_recovery_image:
2281 output_sink("etc/recovery.img", recovery_img.data)
2282
2283 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002284 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002285 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002286 # With system-root-image, boot and recovery images will have mismatching
2287 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2288 # to handle such a case.
2289 if system_root_image:
2290 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002291 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002292 assert not os.path.exists(path)
2293 else:
2294 diff_program = ["imgdiff"]
2295 if os.path.exists(path):
2296 diff_program.append("-b")
2297 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002298 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002299 else:
2300 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002301
2302 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2303 _, _, patch = d.ComputePatch()
2304 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002305
Dan Albertebb19aa2015-03-27 19:11:53 -07002306 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002307 # The following GetTypeAndDevice()s need to use the path in the target
2308 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002309 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2310 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2311 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002312 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002313
Tao Baof2cffbd2015-07-22 12:33:18 -07002314 if full_recovery_image:
2315 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002316if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2317 applypatch \\
2318 --flash /system/etc/recovery.img \\
2319 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2320 log -t recovery "Installing new recovery image: succeeded" || \\
2321 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002322else
2323 log -t recovery "Recovery image already installed"
2324fi
2325""" % {'type': recovery_type,
2326 'device': recovery_device,
2327 'sha1': recovery_img.sha1,
2328 'size': recovery_img.size}
2329 else:
2330 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002331if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2332 applypatch %(bonus_args)s \\
2333 --patch /system/recovery-from-boot.p \\
2334 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2335 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2336 log -t recovery "Installing new recovery image: succeeded" || \\
2337 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002338else
2339 log -t recovery "Recovery image already installed"
2340fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002341""" % {'boot_size': boot_img.size,
2342 'boot_sha1': boot_img.sha1,
2343 'recovery_size': recovery_img.size,
2344 'recovery_sha1': recovery_img.sha1,
2345 'boot_type': boot_type,
2346 'boot_device': boot_device,
2347 'recovery_type': recovery_type,
2348 'recovery_device': recovery_device,
2349 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002350
2351 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002352 # in the L release.
2353 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002354
Tao Bao32fcdab2018-10-12 10:30:39 -07002355 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002356
Tao Baoda30cfa2017-12-01 16:19:46 -08002357 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002358
2359
2360class DynamicPartitionUpdate(object):
2361 def __init__(self, src_group=None, tgt_group=None, progress=None,
2362 block_difference=None):
2363 self.src_group = src_group
2364 self.tgt_group = tgt_group
2365 self.progress = progress
2366 self.block_difference = block_difference
2367
2368 @property
2369 def src_size(self):
2370 if not self.block_difference:
2371 return 0
2372 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2373
2374 @property
2375 def tgt_size(self):
2376 if not self.block_difference:
2377 return 0
2378 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2379
2380 @staticmethod
2381 def _GetSparseImageSize(img):
2382 if not img:
2383 return 0
2384 return img.blocksize * img.total_blocks
2385
2386
2387class DynamicGroupUpdate(object):
2388 def __init__(self, src_size=None, tgt_size=None):
2389 # None: group does not exist. 0: no size limits.
2390 self.src_size = src_size
2391 self.tgt_size = tgt_size
2392
2393
2394class DynamicPartitionsDifference(object):
2395 def __init__(self, info_dict, block_diffs, progress_dict=None,
2396 source_info_dict=None):
2397 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002398 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002399
2400 self._remove_all_before_apply = False
2401 if source_info_dict is None:
2402 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002403 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002404
Tao Baof1113e92019-06-18 12:10:14 -07002405 block_diff_dict = collections.OrderedDict(
2406 [(e.partition, e) for e in block_diffs])
2407
Yifan Hong10c530d2018-12-27 17:34:18 -08002408 assert len(block_diff_dict) == len(block_diffs), \
2409 "Duplicated BlockDifference object for {}".format(
2410 [partition for partition, count in
2411 collections.Counter(e.partition for e in block_diffs).items()
2412 if count > 1])
2413
Yifan Hong79997e52019-01-23 16:56:19 -08002414 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002415
2416 for p, block_diff in block_diff_dict.items():
2417 self._partition_updates[p] = DynamicPartitionUpdate()
2418 self._partition_updates[p].block_difference = block_diff
2419
2420 for p, progress in progress_dict.items():
2421 if p in self._partition_updates:
2422 self._partition_updates[p].progress = progress
2423
2424 tgt_groups = shlex.split(info_dict.get(
2425 "super_partition_groups", "").strip())
2426 src_groups = shlex.split(source_info_dict.get(
2427 "super_partition_groups", "").strip())
2428
2429 for g in tgt_groups:
2430 for p in shlex.split(info_dict.get(
2431 "super_%s_partition_list" % g, "").strip()):
2432 assert p in self._partition_updates, \
2433 "{} is in target super_{}_partition_list but no BlockDifference " \
2434 "object is provided.".format(p, g)
2435 self._partition_updates[p].tgt_group = g
2436
2437 for g in src_groups:
2438 for p in shlex.split(source_info_dict.get(
2439 "super_%s_partition_list" % g, "").strip()):
2440 assert p in self._partition_updates, \
2441 "{} is in source super_{}_partition_list but no BlockDifference " \
2442 "object is provided.".format(p, g)
2443 self._partition_updates[p].src_group = g
2444
Yifan Hong45433e42019-01-18 13:55:25 -08002445 target_dynamic_partitions = set(shlex.split(info_dict.get(
2446 "dynamic_partition_list", "").strip()))
2447 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2448 if u.tgt_size)
2449 assert block_diffs_with_target == target_dynamic_partitions, \
2450 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2451 list(target_dynamic_partitions), list(block_diffs_with_target))
2452
2453 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2454 "dynamic_partition_list", "").strip()))
2455 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2456 if u.src_size)
2457 assert block_diffs_with_source == source_dynamic_partitions, \
2458 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2459 list(source_dynamic_partitions), list(block_diffs_with_source))
2460
Yifan Hong10c530d2018-12-27 17:34:18 -08002461 if self._partition_updates:
2462 logger.info("Updating dynamic partitions %s",
2463 self._partition_updates.keys())
2464
Yifan Hong79997e52019-01-23 16:56:19 -08002465 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002466
2467 for g in tgt_groups:
2468 self._group_updates[g] = DynamicGroupUpdate()
2469 self._group_updates[g].tgt_size = int(info_dict.get(
2470 "super_%s_group_size" % g, "0").strip())
2471
2472 for g in src_groups:
2473 if g not in self._group_updates:
2474 self._group_updates[g] = DynamicGroupUpdate()
2475 self._group_updates[g].src_size = int(source_info_dict.get(
2476 "super_%s_group_size" % g, "0").strip())
2477
2478 self._Compute()
2479
2480 def WriteScript(self, script, output_zip, write_verify_script=False):
2481 script.Comment('--- Start patching dynamic partitions ---')
2482 for p, u in self._partition_updates.items():
2483 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2484 script.Comment('Patch partition %s' % p)
2485 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2486 write_verify_script=False)
2487
2488 op_list_path = MakeTempFile()
2489 with open(op_list_path, 'w') as f:
2490 for line in self._op_list:
2491 f.write('{}\n'.format(line))
2492
2493 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2494
2495 script.Comment('Update dynamic partition metadata')
2496 script.AppendExtra('assert(update_dynamic_partitions('
2497 'package_extract_file("dynamic_partitions_op_list")));')
2498
2499 if write_verify_script:
2500 for p, u in self._partition_updates.items():
2501 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2502 u.block_difference.WritePostInstallVerifyScript(script)
2503 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2504
2505 for p, u in self._partition_updates.items():
2506 if u.tgt_size and u.src_size <= u.tgt_size:
2507 script.Comment('Patch partition %s' % p)
2508 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2509 write_verify_script=write_verify_script)
2510 if write_verify_script:
2511 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2512
2513 script.Comment('--- End patching dynamic partitions ---')
2514
2515 def _Compute(self):
2516 self._op_list = list()
2517
2518 def append(line):
2519 self._op_list.append(line)
2520
2521 def comment(line):
2522 self._op_list.append("# %s" % line)
2523
2524 if self._remove_all_before_apply:
2525 comment('Remove all existing dynamic partitions and groups before '
2526 'applying full OTA')
2527 append('remove_all_groups')
2528
2529 for p, u in self._partition_updates.items():
2530 if u.src_group and not u.tgt_group:
2531 append('remove %s' % p)
2532
2533 for p, u in self._partition_updates.items():
2534 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2535 comment('Move partition %s from %s to default' % (p, u.src_group))
2536 append('move %s default' % p)
2537
2538 for p, u in self._partition_updates.items():
2539 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2540 comment('Shrink partition %s from %d to %d' %
2541 (p, u.src_size, u.tgt_size))
2542 append('resize %s %s' % (p, u.tgt_size))
2543
2544 for g, u in self._group_updates.items():
2545 if u.src_size is not None and u.tgt_size is None:
2546 append('remove_group %s' % g)
2547 if (u.src_size is not None and u.tgt_size is not None and
2548 u.src_size > u.tgt_size):
2549 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2550 append('resize_group %s %d' % (g, u.tgt_size))
2551
2552 for g, u in self._group_updates.items():
2553 if u.src_size is None and u.tgt_size is not None:
2554 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2555 append('add_group %s %d' % (g, u.tgt_size))
2556 if (u.src_size is not None and u.tgt_size is not None and
2557 u.src_size < u.tgt_size):
2558 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2559 append('resize_group %s %d' % (g, u.tgt_size))
2560
2561 for p, u in self._partition_updates.items():
2562 if u.tgt_group and not u.src_group:
2563 comment('Add partition %s to group %s' % (p, u.tgt_group))
2564 append('add %s %s' % (p, u.tgt_group))
2565
2566 for p, u in self._partition_updates.items():
2567 if u.tgt_size and u.src_size < u.tgt_size:
2568 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2569 append('resize %s %d' % (p, u.tgt_size))
2570
2571 for p, u in self._partition_updates.items():
2572 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2573 comment('Move partition %s from default to %s' %
2574 (p, u.tgt_group))
2575 append('move %s %s' % (p, u.tgt_group))