blob: 7cff831e66eb5ad53e1878f765a5d24682973762 [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
Yifan Hong10c530d2018-12-27 17:34:18 -080017import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070018import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070019import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070020import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070021import getopt
22import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010023import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070024import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070025import json
26import logging
27import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070028import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080029import platform
Doug Zongkereef39442009-04-02 12:14:19 -070030import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070031import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070032import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080033import string
Doug Zongkereef39442009-04-02 12:14:19 -070034import subprocess
35import sys
36import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070037import threading
38import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070039import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080040from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070041
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070042import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070044
Tao Bao32fcdab2018-10-12 10:30:39 -070045logger = logging.getLogger(__name__)
46
Tao Bao986ee862018-10-04 15:46:16 -070047
Dan Albert8b72aef2015-03-23 19:13:21 -070048class Options(object):
49 def __init__(self):
Pavel Salomatov32676552019-03-06 20:00:45 +030050 base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
51 if base_out_path is None:
52 base_search_path = "out"
53 else:
Tao Bao2cc0ca12019-03-15 10:44:43 -070054 base_search_path = os.path.join(base_out_path,
55 os.path.basename(os.getcwd()))
Pavel Salomatov32676552019-03-06 20:00:45 +030056
Dan Albert8b72aef2015-03-23 19:13:21 -070057 platform_search_path = {
Pavel Salomatov32676552019-03-06 20:00:45 +030058 "linux2": os.path.join(base_search_path, "host/linux-x86"),
59 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070060 }
Doug Zongker85448772014-09-09 14:59:20 -070061
Tao Bao76def242017-11-21 09:25:31 -080062 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070063 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080064 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070065 self.extra_signapk_args = []
66 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080067 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070068 self.public_key_suffix = ".x509.pem"
69 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070070 # use otatools built boot_signer by default
71 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070072 self.boot_signer_args = []
73 self.verity_signer_path = None
74 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.verbose = False
76 self.tempfiles = []
77 self.device_specific = None
78 self.extras = {}
79 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070080 self.source_info_dict = None
81 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070082 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070083 # Stash size cannot exceed cache_size * threshold.
84 self.cache_size = None
85 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070086
87
88OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070089
Tao Bao71197512018-10-11 14:08:45 -070090# The block size that's used across the releasetools scripts.
91BLOCK_SIZE = 4096
92
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080093# Values for "certificate" in apkcerts that mean special things.
94SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
95
Tao Bao5cc0abb2019-03-21 10:18:05 -070096# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
97# that system_other is not in the list because we don't want to include its
98# descriptor into vbmeta.img.
99AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'product_services',
100 'recovery', 'system', 'vendor')
Tao Bao9dd909e2017-11-14 11:27:32 -0800101
Tao Bao08c190f2019-06-03 23:07:58 -0700102# Chained VBMeta partitions.
103AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
104
Tianjie Xu861f4132018-09-12 11:49:33 -0700105# Partitions that should have their care_map added to META/care_map.pb
106PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
107 'odm')
108
109
Tianjie Xu209db462016-05-24 17:34:52 -0700110class ErrorCode(object):
111 """Define error_codes for failures that happen during the actual
112 update package installation.
113
114 Error codes 0-999 are reserved for failures before the package
115 installation (i.e. low battery, package verification failure).
116 Detailed code in 'bootable/recovery/error_code.h' """
117
118 SYSTEM_VERIFICATION_FAILURE = 1000
119 SYSTEM_UPDATE_FAILURE = 1001
120 SYSTEM_UNEXPECTED_CONTENTS = 1002
121 SYSTEM_NONZERO_CONTENTS = 1003
122 SYSTEM_RECOVER_FAILURE = 1004
123 VENDOR_VERIFICATION_FAILURE = 2000
124 VENDOR_UPDATE_FAILURE = 2001
125 VENDOR_UNEXPECTED_CONTENTS = 2002
126 VENDOR_NONZERO_CONTENTS = 2003
127 VENDOR_RECOVER_FAILURE = 2004
128 OEM_PROP_MISMATCH = 3000
129 FINGERPRINT_MISMATCH = 3001
130 THUMBPRINT_MISMATCH = 3002
131 OLDER_BUILD = 3003
132 DEVICE_MISMATCH = 3004
133 BAD_PATCH_FILE = 3005
134 INSUFFICIENT_CACHE_SPACE = 3006
135 TUNE_PARTITION_FAILURE = 3007
136 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800137
Tao Bao80921982018-03-21 21:02:19 -0700138
Dan Albert8b72aef2015-03-23 19:13:21 -0700139class ExternalError(RuntimeError):
140 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700141
142
Tao Bao32fcdab2018-10-12 10:30:39 -0700143def InitLogging():
144 DEFAULT_LOGGING_CONFIG = {
145 'version': 1,
146 'disable_existing_loggers': False,
147 'formatters': {
148 'standard': {
149 'format':
150 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
151 'datefmt': '%Y-%m-%d %H:%M:%S',
152 },
153 },
154 'handlers': {
155 'default': {
156 'class': 'logging.StreamHandler',
157 'formatter': 'standard',
158 },
159 },
160 'loggers': {
161 '': {
162 'handlers': ['default'],
163 'level': 'WARNING',
164 'propagate': True,
165 }
166 }
167 }
168 env_config = os.getenv('LOGGING_CONFIG')
169 if env_config:
170 with open(env_config) as f:
171 config = json.load(f)
172 else:
173 config = DEFAULT_LOGGING_CONFIG
174
175 # Increase the logging level for verbose mode.
176 if OPTIONS.verbose:
177 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
178 config['loggers']['']['level'] = 'INFO'
179
180 logging.config.dictConfig(config)
181
182
Tao Bao39451582017-05-04 11:10:47 -0700183def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700184 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700185
Tao Bao73dd4f42018-10-04 16:25:33 -0700186 Args:
187 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700188 verbose: Whether the commands should be shown. Default to the global
189 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700190 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
191 stdin, etc. stdout and stderr will default to subprocess.PIPE and
192 subprocess.STDOUT respectively unless caller specifies any of them.
193
194 Returns:
195 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700196 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700197 if 'stdout' not in kwargs and 'stderr' not in kwargs:
198 kwargs['stdout'] = subprocess.PIPE
199 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700200 # Don't log any if caller explicitly says so.
201 if verbose != False:
202 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700203 return subprocess.Popen(args, **kwargs)
204
205
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800206def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800207 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800208
209 Args:
210 args: The command represented as a list of strings.
211 verbose: Whether the commands should be shown. Default to the global
212 verbosity if unspecified.
213 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
214 stdin, etc. stdout and stderr will default to subprocess.PIPE and
215 subprocess.STDOUT respectively unless caller specifies any of them.
216
Bill Peckham889b0c62019-02-21 18:53:37 -0800217 Raises:
218 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800219 """
220 proc = Run(args, verbose=verbose, **kwargs)
221 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800222
223 if proc.returncode != 0:
224 raise ExternalError(
225 "Failed to run command '{}' (exit code {})".format(
226 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800227
228
Tao Bao986ee862018-10-04 15:46:16 -0700229def RunAndCheckOutput(args, verbose=None, **kwargs):
230 """Runs the given command and returns the output.
231
232 Args:
233 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700234 verbose: Whether the commands should be shown. Default to the global
235 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700236 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
237 stdin, etc. stdout and stderr will default to subprocess.PIPE and
238 subprocess.STDOUT respectively unless caller specifies any of them.
239
240 Returns:
241 The output string.
242
243 Raises:
244 ExternalError: On non-zero exit from the command.
245 """
Tao Bao986ee862018-10-04 15:46:16 -0700246 proc = Run(args, verbose=verbose, **kwargs)
247 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700248 # Don't log any if caller explicitly says so.
249 if verbose != False:
250 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700251 if proc.returncode != 0:
252 raise ExternalError(
253 "Failed to run command '{}' (exit code {}):\n{}".format(
254 args, proc.returncode, output))
255 return output
256
257
Tao Baoc765cca2018-01-31 17:32:40 -0800258def RoundUpTo4K(value):
259 rounded_up = value + 4095
260 return rounded_up - (rounded_up % 4096)
261
262
Ying Wang7e6d4e42010-12-13 16:25:36 -0800263def CloseInheritedPipes():
264 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
265 before doing other work."""
266 if platform.system() != "Darwin":
267 return
268 for d in range(3, 1025):
269 try:
270 stat = os.fstat(d)
271 if stat is not None:
272 pipebit = stat[0] & 0x1000
273 if pipebit != 0:
274 os.close(d)
275 except OSError:
276 pass
277
278
Tao Bao410ad8b2018-08-24 12:08:38 -0700279def LoadInfoDict(input_file, repacking=False):
280 """Loads the key/value pairs from the given input target_files.
281
282 It reads `META/misc_info.txt` file in the target_files input, does sanity
283 checks and returns the parsed key/value pairs for to the given build. It's
284 usually called early when working on input target_files files, e.g. when
285 generating OTAs, or signing builds. Note that the function may be called
286 against an old target_files file (i.e. from past dessert releases). So the
287 property parsing needs to be backward compatible.
288
289 In a `META/misc_info.txt`, a few properties are stored as links to the files
290 in the PRODUCT_OUT directory. It works fine with the build system. However,
291 they are no longer available when (re)generating images from target_files zip.
292 When `repacking` is True, redirect these properties to the actual files in the
293 unzipped directory.
294
295 Args:
296 input_file: The input target_files file, which could be an open
297 zipfile.ZipFile instance, or a str for the dir that contains the files
298 unzipped from a target_files file.
299 repacking: Whether it's trying repack an target_files file after loading the
300 info dict (default: False). If so, it will rewrite a few loaded
301 properties (e.g. selinux_fc, root_dir) to point to the actual files in
302 target_files file. When doing repacking, `input_file` must be a dir.
303
304 Returns:
305 A dict that contains the parsed key/value pairs.
306
307 Raises:
308 AssertionError: On invalid input arguments.
309 ValueError: On malformed input values.
310 """
311 if repacking:
312 assert isinstance(input_file, str), \
313 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700314
Doug Zongkerc9253822014-02-04 12:17:58 -0800315 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700316 if isinstance(input_file, zipfile.ZipFile):
317 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800318 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700319 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800320 try:
321 with open(path) as f:
322 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700323 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800324 if e.errno == errno.ENOENT:
325 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800326
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700327 try:
Michael Runge6e836112014-04-15 17:40:21 -0700328 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700329 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700330 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700331
Tao Bao410ad8b2018-08-24 12:08:38 -0700332 if "recovery_api_version" not in d:
333 raise ValueError("Failed to find 'recovery_api_version'")
334 if "fstab_version" not in d:
335 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800336
Tao Bao410ad8b2018-08-24 12:08:38 -0700337 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700338 # "selinux_fc" properties should point to the file_contexts files
339 # (file_contexts.bin) under META/.
340 for key in d:
341 if key.endswith("selinux_fc"):
342 fc_basename = os.path.basename(d[key])
343 fc_config = os.path.join(input_file, "META", fc_basename)
344 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700345
Daniel Norman72c626f2019-05-13 15:58:14 -0700346 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700347
Tom Cherryd14b8952018-08-09 14:26:00 -0700348 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700349 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700350 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700351 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700352
Tao Baof54216f2016-03-29 15:12:37 -0700353 # Redirect {system,vendor}_base_fs_file.
354 if "system_base_fs_file" in d:
355 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700356 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700357 if os.path.exists(system_base_fs_file):
358 d["system_base_fs_file"] = system_base_fs_file
359 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700360 logger.warning(
361 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700362 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700363
364 if "vendor_base_fs_file" in d:
365 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700366 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700367 if os.path.exists(vendor_base_fs_file):
368 d["vendor_base_fs_file"] = vendor_base_fs_file
369 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700370 logger.warning(
371 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700372 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700373
Doug Zongker37974732010-09-16 17:44:38 -0700374 def makeint(key):
375 if key in d:
376 d[key] = int(d[key], 0)
377
378 makeint("recovery_api_version")
379 makeint("blocksize")
380 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700381 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700382 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700383 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700384 makeint("recovery_size")
385 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800386 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700387
Tao Baoa57ab9f2018-08-24 12:08:38 -0700388 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
389 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
390 # cases, since it may load the info_dict from an old build (e.g. when
391 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800392 system_root_image = d.get("system_root_image") == "true"
393 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700394 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700395 if isinstance(input_file, zipfile.ZipFile):
396 if recovery_fstab_path not in input_file.namelist():
397 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
398 else:
399 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
400 if not os.path.exists(path):
401 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800402 d["fstab"] = LoadRecoveryFSTab(
403 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700404
Tao Bao76def242017-11-21 09:25:31 -0800405 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700406 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700407 if isinstance(input_file, zipfile.ZipFile):
408 if recovery_fstab_path not in input_file.namelist():
409 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
410 else:
411 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
412 if not os.path.exists(path):
413 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800414 d["fstab"] = LoadRecoveryFSTab(
415 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700416
Tianjie Xucfa86222016-03-07 16:31:19 -0800417 else:
418 d["fstab"] = None
419
Tianjie Xu861f4132018-09-12 11:49:33 -0700420 # Tries to load the build props for all partitions with care_map, including
421 # system and vendor.
422 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800423 partition_prop = "{}.build.prop".format(partition)
424 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700425 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800426 # Some partition might use /<partition>/etc/build.prop as the new path.
427 # TODO: try new path first when majority of them switch to the new path.
428 if not d[partition_prop]:
429 d[partition_prop] = LoadBuildProp(
430 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700431 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800432
433 # Set up the salt (based on fingerprint or thumbprint) that will be used when
434 # adding AVB footer.
435 if d.get("avb_enable") == "true":
436 fp = None
437 if "build.prop" in d:
438 build_prop = d["build.prop"]
439 if "ro.build.fingerprint" in build_prop:
440 fp = build_prop["ro.build.fingerprint"]
441 elif "ro.build.thumbprint" in build_prop:
442 fp = build_prop["ro.build.thumbprint"]
443 if fp:
444 d["avb_salt"] = sha256(fp).hexdigest()
445
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700446 return d
447
Tao Baod1de6f32017-03-01 16:38:48 -0800448
Tao Baobcd1d162017-08-26 13:10:26 -0700449def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700450 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700451 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700452 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700453 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700454 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700455 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700456
Tao Baod1de6f32017-03-01 16:38:48 -0800457
Michael Runge6e836112014-04-15 17:40:21 -0700458def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700459 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700460 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700461 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700462 if not line or line.startswith("#"):
463 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700464 if "=" in line:
465 name, value = line.split("=", 1)
466 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700467 return d
468
Tao Baod1de6f32017-03-01 16:38:48 -0800469
Tianjie Xucfa86222016-03-07 16:31:19 -0800470def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
471 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700472 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800473 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700474 self.mount_point = mount_point
475 self.fs_type = fs_type
476 self.device = device
477 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700478 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700479
480 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800481 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700482 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700483 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700484 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700485
Tao Baod1de6f32017-03-01 16:38:48 -0800486 assert fstab_version == 2
487
488 d = {}
489 for line in data.split("\n"):
490 line = line.strip()
491 if not line or line.startswith("#"):
492 continue
493
494 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
495 pieces = line.split()
496 if len(pieces) != 5:
497 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
498
499 # Ignore entries that are managed by vold.
500 options = pieces[4]
501 if "voldmanaged=" in options:
502 continue
503
504 # It's a good line, parse it.
505 length = 0
506 options = options.split(",")
507 for i in options:
508 if i.startswith("length="):
509 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800510 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800511 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700512 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800513
Tao Baod1de6f32017-03-01 16:38:48 -0800514 mount_flags = pieces[3]
515 # Honor the SELinux context if present.
516 context = None
517 for i in mount_flags.split(","):
518 if i.startswith("context="):
519 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800520
Tao Baod1de6f32017-03-01 16:38:48 -0800521 mount_point = pieces[1]
522 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
523 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800524
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700525 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700526 # system. Other areas assume system is always at "/system" so point /system
527 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700528 if system_root_image:
529 assert not d.has_key("/system") and d.has_key("/")
530 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700531 return d
532
533
Doug Zongker37974732010-09-16 17:44:38 -0700534def DumpInfoDict(d):
535 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700536 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700537
Dan Albert8b72aef2015-03-23 19:13:21 -0700538
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800539def AppendAVBSigningArgs(cmd, partition):
540 """Append signing arguments for avbtool."""
541 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
542 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
543 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
544 if key_path and algorithm:
545 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700546 avb_salt = OPTIONS.info_dict.get("avb_salt")
547 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700548 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700549 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800550
551
Tao Bao02a08592018-07-22 12:40:45 -0700552def GetAvbChainedPartitionArg(partition, info_dict, key=None):
553 """Constructs and returns the arg to build or verify a chained partition.
554
555 Args:
556 partition: The partition name.
557 info_dict: The info dict to look up the key info and rollback index
558 location.
559 key: The key to be used for building or verifying the partition. Defaults to
560 the key listed in info_dict.
561
562 Returns:
563 A string of form "partition:rollback_index_location:key" that can be used to
564 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700565 """
566 if key is None:
567 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao2cc0ca12019-03-15 10:44:43 -0700568 pubkey_path = ExtractAvbPublicKey(key)
Tao Bao02a08592018-07-22 12:40:45 -0700569 rollback_index_location = info_dict[
570 "avb_" + partition + "_rollback_index_location"]
571 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
572
573
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700574def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800575 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700576 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700577
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700578 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800579 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
580 we are building a two-step special image (i.e. building a recovery image to
581 be loaded into /boot in two-step OTAs).
582
583 Return the image data, or None if sourcedir does not appear to contains files
584 for building the requested image.
585 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700586
587 def make_ramdisk():
588 ramdisk_img = tempfile.NamedTemporaryFile()
589
590 if os.access(fs_config_file, os.F_OK):
591 cmd = ["mkbootfs", "-f", fs_config_file,
592 os.path.join(sourcedir, "RAMDISK")]
593 else:
594 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
595 p1 = Run(cmd, stdout=subprocess.PIPE)
596 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
597
598 p2.wait()
599 p1.wait()
600 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
601 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
602
603 return ramdisk_img
604
605 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
606 return None
607
608 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700609 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700610
Doug Zongkerd5131602012-08-02 14:46:42 -0700611 if info_dict is None:
612 info_dict = OPTIONS.info_dict
613
Doug Zongkereef39442009-04-02 12:14:19 -0700614 img = tempfile.NamedTemporaryFile()
615
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700616 if has_ramdisk:
617 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700618
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800619 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
620 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
621
622 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700623
Benoit Fradina45a8682014-07-14 21:00:43 +0200624 fn = os.path.join(sourcedir, "second")
625 if os.access(fn, os.F_OK):
626 cmd.append("--second")
627 cmd.append(fn)
628
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800629 fn = os.path.join(sourcedir, "dtb")
630 if os.access(fn, os.F_OK):
631 cmd.append("--dtb")
632 cmd.append(fn)
633
Doug Zongker171f1cd2009-06-15 22:36:37 -0700634 fn = os.path.join(sourcedir, "cmdline")
635 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700636 cmd.append("--cmdline")
637 cmd.append(open(fn).read().rstrip("\n"))
638
639 fn = os.path.join(sourcedir, "base")
640 if os.access(fn, os.F_OK):
641 cmd.append("--base")
642 cmd.append(open(fn).read().rstrip("\n"))
643
Ying Wang4de6b5b2010-08-25 14:29:34 -0700644 fn = os.path.join(sourcedir, "pagesize")
645 if os.access(fn, os.F_OK):
646 cmd.append("--pagesize")
647 cmd.append(open(fn).read().rstrip("\n"))
648
Tao Bao76def242017-11-21 09:25:31 -0800649 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700650 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700651 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700652
Tao Bao76def242017-11-21 09:25:31 -0800653 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000654 if args and args.strip():
655 cmd.extend(shlex.split(args))
656
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700657 if has_ramdisk:
658 cmd.extend(["--ramdisk", ramdisk_img.name])
659
Tao Baod95e9fd2015-03-29 23:07:41 -0700660 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800661 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700662 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700663 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700664 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700665 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700666
Tao Baobf70c3182017-07-11 17:27:55 -0700667 # "boot" or "recovery", without extension.
668 partition_name = os.path.basename(sourcedir).lower()
669
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800670 if partition_name == "recovery":
671 if info_dict.get("include_recovery_dtbo") == "true":
672 fn = os.path.join(sourcedir, "recovery_dtbo")
673 cmd.extend(["--recovery_dtbo", fn])
674 if info_dict.get("include_recovery_acpio") == "true":
675 fn = os.path.join(sourcedir, "recovery_acpio")
676 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700677
Tao Bao986ee862018-10-04 15:46:16 -0700678 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700679
Tao Bao76def242017-11-21 09:25:31 -0800680 if (info_dict.get("boot_signer") == "true" and
681 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800682 # Hard-code the path as "/boot" for two-step special recovery image (which
683 # will be loaded into /boot during the two-step OTA).
684 if two_step_image:
685 path = "/boot"
686 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700687 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700688 cmd = [OPTIONS.boot_signer_path]
689 cmd.extend(OPTIONS.boot_signer_args)
690 cmd.extend([path, img.name,
691 info_dict["verity_key"] + ".pk8",
692 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700693 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700694
Tao Baod95e9fd2015-03-29 23:07:41 -0700695 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800696 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700697 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700698 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800699 # We have switched from the prebuilt futility binary to using the tool
700 # (futility-host) built from the source. Override the setting in the old
701 # TF.zip.
702 futility = info_dict["futility"]
703 if futility.startswith("prebuilts/"):
704 futility = "futility-host"
705 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700706 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700707 info_dict["vboot_key"] + ".vbprivk",
708 info_dict["vboot_subkey"] + ".vbprivk",
709 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700710 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700711 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700712
Tao Baof3282b42015-04-01 11:21:55 -0700713 # Clean up the temp files.
714 img_unsigned.close()
715 img_keyblock.close()
716
David Zeuthen8fecb282017-12-01 16:24:01 -0500717 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800718 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700719 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500720 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400721 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700722 "--partition_size", str(part_size), "--partition_name",
723 partition_name]
724 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500725 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400726 if args and args.strip():
727 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700728 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500729
730 img.seek(os.SEEK_SET, 0)
731 data = img.read()
732
733 if has_ramdisk:
734 ramdisk_img.close()
735 img.close()
736
737 return data
738
739
Doug Zongkerd5131602012-08-02 14:46:42 -0700740def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800741 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700742 """Return a File object with the desired bootable image.
743
744 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
745 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
746 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700747
Doug Zongker55d93282011-01-25 17:03:34 -0800748 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
749 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700750 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800751 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700752
753 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
754 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700755 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700756 return File.FromLocalFile(name, prebuilt_path)
757
Tao Bao32fcdab2018-10-12 10:30:39 -0700758 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700759
760 if info_dict is None:
761 info_dict = OPTIONS.info_dict
762
763 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800764 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
765 # for recovery.
766 has_ramdisk = (info_dict.get("system_root_image") != "true" or
767 prebuilt_name != "boot.img" or
768 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700769
Doug Zongker6f1d0312014-08-22 08:07:12 -0700770 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400771 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
772 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800773 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700774 if data:
775 return File(name, data)
776 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800777
Doug Zongkereef39442009-04-02 12:14:19 -0700778
Narayan Kamatha07bf042017-08-14 14:49:21 +0100779def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800780 """Gunzips the given gzip compressed file to a given output file."""
781 with gzip.open(in_filename, "rb") as in_file, \
782 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100783 shutil.copyfileobj(in_file, out_file)
784
785
Tao Bao0ff15de2019-03-20 11:26:06 -0700786def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800787 """Unzips the archive to the given directory.
788
789 Args:
790 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800791 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700792 patterns: Files to unzip from the archive. If omitted, will unzip the entire
793 archvie. Non-matching patterns will be filtered out. If there's no match
794 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800795 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800796 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700797 if patterns is not None:
798 # Filter out non-matching patterns. unzip will complain otherwise.
799 with zipfile.ZipFile(filename) as input_zip:
800 names = input_zip.namelist()
801 filtered = [
802 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
803
804 # There isn't any matching files. Don't unzip anything.
805 if not filtered:
806 return
807 cmd.extend(filtered)
808
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800809 RunAndCheckOutput(cmd)
810
811
Doug Zongker75f17362009-12-08 13:46:44 -0800812def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800813 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800814
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800815 Args:
816 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
817 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
818
819 pattern: Files to unzip from the archive. If omitted, will unzip the entire
820 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800821
Tao Bao1c830bf2017-12-25 10:43:47 -0800822 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800823 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800824 """
Doug Zongkereef39442009-04-02 12:14:19 -0700825
Tao Bao1c830bf2017-12-25 10:43:47 -0800826 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800827 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
828 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800829 UnzipToDir(m.group(1), tmp, pattern)
830 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800831 filename = m.group(1)
832 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800833 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800834
Tao Baodba59ee2018-01-09 13:21:02 -0800835 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700836
837
Yifan Hong8a66a712019-04-04 15:37:57 -0700838def GetUserImage(which, tmpdir, input_zip,
839 info_dict=None,
840 allow_shared_blocks=None,
841 hashtree_info_generator=None,
842 reset_file_map=False):
843 """Returns an Image object suitable for passing to BlockImageDiff.
844
845 This function loads the specified image from the given path. If the specified
846 image is sparse, it also performs additional processing for OTA purpose. For
847 example, it always adds block 0 to clobbered blocks list. It also detects
848 files that cannot be reconstructed from the block list, for whom we should
849 avoid applying imgdiff.
850
851 Args:
852 which: The partition name.
853 tmpdir: The directory that contains the prebuilt image and block map file.
854 input_zip: The target-files ZIP archive.
855 info_dict: The dict to be looked up for relevant info.
856 allow_shared_blocks: If image is sparse, whether having shared blocks is
857 allowed. If none, it is looked up from info_dict.
858 hashtree_info_generator: If present and image is sparse, generates the
859 hashtree_info for this sparse image.
860 reset_file_map: If true and image is sparse, reset file map before returning
861 the image.
862 Returns:
863 A Image object. If it is a sparse image and reset_file_map is False, the
864 image will have file_map info loaded.
865 """
Tao Baoc1a1ec32019-06-18 16:29:37 -0700866 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -0700867 info_dict = LoadInfoDict(input_zip)
868
869 is_sparse = info_dict.get("extfs_sparse_flag")
870
871 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
872 # shared blocks (i.e. some blocks will show up in multiple files' block
873 # list). We can only allocate such shared blocks to the first "owner", and
874 # disable imgdiff for all later occurrences.
875 if allow_shared_blocks is None:
876 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
877
878 if is_sparse:
879 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
880 hashtree_info_generator)
881 if reset_file_map:
882 img.ResetFileMap()
883 return img
884 else:
885 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
886
887
888def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
889 """Returns a Image object suitable for passing to BlockImageDiff.
890
891 This function loads the specified non-sparse image from the given path.
892
893 Args:
894 which: The partition name.
895 tmpdir: The directory that contains the prebuilt image and block map file.
896 Returns:
897 A Image object.
898 """
899 path = os.path.join(tmpdir, "IMAGES", which + ".img")
900 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
901
902 # The image and map files must have been created prior to calling
903 # ota_from_target_files.py (since LMP).
904 assert os.path.exists(path) and os.path.exists(mappath)
905
906 return blockimgdiff.FileImage(path, hashtree_info_generator=
907 hashtree_info_generator)
908
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700909def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
910 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800911 """Returns a SparseImage object suitable for passing to BlockImageDiff.
912
913 This function loads the specified sparse image from the given path, and
914 performs additional processing for OTA purpose. For example, it always adds
915 block 0 to clobbered blocks list. It also detects files that cannot be
916 reconstructed from the block list, for whom we should avoid applying imgdiff.
917
918 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700919 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800920 tmpdir: The directory that contains the prebuilt image and block map file.
921 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800922 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700923 hashtree_info_generator: If present, generates the hashtree_info for this
924 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800925 Returns:
926 A SparseImage object, with file_map info loaded.
927 """
Tao Baoc765cca2018-01-31 17:32:40 -0800928 path = os.path.join(tmpdir, "IMAGES", which + ".img")
929 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
930
931 # The image and map files must have been created prior to calling
932 # ota_from_target_files.py (since LMP).
933 assert os.path.exists(path) and os.path.exists(mappath)
934
935 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
936 # it to clobbered_blocks so that it will be written to the target
937 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
938 clobbered_blocks = "0"
939
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700940 image = sparse_img.SparseImage(
941 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
942 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800943
944 # block.map may contain less blocks, because mke2fs may skip allocating blocks
945 # if they contain all zeros. We can't reconstruct such a file from its block
946 # list. Tag such entries accordingly. (Bug: 65213616)
947 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800948 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700949 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800950 continue
951
Tom Cherryd14b8952018-08-09 14:26:00 -0700952 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
953 # filename listed in system.map may contain an additional leading slash
954 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
955 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700956 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
957
Tom Cherryd14b8952018-08-09 14:26:00 -0700958 # Special handling another case, where files not under /system
959 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700960 if which == 'system' and not arcname.startswith('SYSTEM'):
961 arcname = 'ROOT/' + arcname
962
963 assert arcname in input_zip.namelist(), \
964 "Failed to find the ZIP entry for {}".format(entry)
965
Tao Baoc765cca2018-01-31 17:32:40 -0800966 info = input_zip.getinfo(arcname)
967 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800968
969 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800970 # image, check the original block list to determine its completeness. Note
971 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800972 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800973 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800974
Tao Baoc765cca2018-01-31 17:32:40 -0800975 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
976 ranges.extra['incomplete'] = True
977
978 return image
979
980
Doug Zongkereef39442009-04-02 12:14:19 -0700981def GetKeyPasswords(keylist):
982 """Given a list of keys, prompt the user to enter passwords for
983 those which require them. Return a {key: password} dict. password
984 will be None if the key has no password."""
985
Doug Zongker8ce7c252009-05-22 13:34:54 -0700986 no_passwords = []
987 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700988 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700989 devnull = open("/dev/null", "w+b")
990 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800991 # We don't need a password for things that aren't really keys.
992 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700993 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700994 continue
995
T.R. Fullhart37e10522013-03-18 10:31:26 -0700996 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700997 "-inform", "DER", "-nocrypt"],
998 stdin=devnull.fileno(),
999 stdout=devnull.fileno(),
1000 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001001 p.communicate()
1002 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001003 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001004 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001005 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001006 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1007 "-inform", "DER", "-passin", "pass:"],
1008 stdin=devnull.fileno(),
1009 stdout=devnull.fileno(),
1010 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001011 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001012 if p.returncode == 0:
1013 # Encrypted key with empty string as password.
1014 key_passwords[k] = ''
1015 elif stderr.startswith('Error decrypting key'):
1016 # Definitely encrypted key.
1017 # It would have said "Error reading key" if it didn't parse correctly.
1018 need_passwords.append(k)
1019 else:
1020 # Potentially, a type of key that openssl doesn't understand.
1021 # We'll let the routines in signapk.jar handle it.
1022 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001023 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001024
T.R. Fullhart37e10522013-03-18 10:31:26 -07001025 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001026 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001027 return key_passwords
1028
1029
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001030def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001031 """Gets the minSdkVersion declared in the APK.
1032
1033 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1034 This can be both a decimal number (API Level) or a codename.
1035
1036 Args:
1037 apk_name: The APK filename.
1038
1039 Returns:
1040 The parsed SDK version string.
1041
1042 Raises:
1043 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001044 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001045 proc = Run(
1046 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1047 stderr=subprocess.PIPE)
1048 stdoutdata, stderrdata = proc.communicate()
1049 if proc.returncode != 0:
1050 raise ExternalError(
1051 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1052 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001053
Tao Baof47bf0f2018-03-21 23:28:51 -07001054 for line in stdoutdata.split("\n"):
1055 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001056 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1057 if m:
1058 return m.group(1)
1059 raise ExternalError("No minSdkVersion returned by aapt")
1060
1061
1062def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001063 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001064
Tao Baof47bf0f2018-03-21 23:28:51 -07001065 If minSdkVersion is set to a codename, it is translated to a number using the
1066 provided map.
1067
1068 Args:
1069 apk_name: The APK filename.
1070
1071 Returns:
1072 The parsed SDK version number.
1073
1074 Raises:
1075 ExternalError: On failing to get the min SDK version number.
1076 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001077 version = GetMinSdkVersion(apk_name)
1078 try:
1079 return int(version)
1080 except ValueError:
1081 # Not a decimal number. Codename?
1082 if version in codename_to_api_level_map:
1083 return codename_to_api_level_map[version]
1084 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001085 raise ExternalError(
1086 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1087 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001088
1089
1090def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001091 codename_to_api_level_map=None, whole_file=False,
1092 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001093 """Sign the input_name zip/jar/apk, producing output_name. Use the
1094 given key and password (the latter may be None if the key does not
1095 have a password.
1096
Doug Zongker951495f2009-08-14 12:44:19 -07001097 If whole_file is true, use the "-w" option to SignApk to embed a
1098 signature that covers the whole file in the archive comment of the
1099 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001100
1101 min_api_level is the API Level (int) of the oldest platform this file may end
1102 up on. If not specified for an APK, the API Level is obtained by interpreting
1103 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1104
1105 codename_to_api_level_map is needed to translate the codename which may be
1106 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001107
1108 Caller may optionally specify extra args to be passed to SignApk, which
1109 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001110 """
Tao Bao76def242017-11-21 09:25:31 -08001111 if codename_to_api_level_map is None:
1112 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001113 if extra_signapk_args is None:
1114 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001115
Alex Klyubin9667b182015-12-10 13:38:50 -08001116 java_library_path = os.path.join(
1117 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1118
Tao Baoe95540e2016-11-08 12:08:53 -08001119 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1120 ["-Djava.library.path=" + java_library_path,
1121 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001122 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001123 if whole_file:
1124 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001125
1126 min_sdk_version = min_api_level
1127 if min_sdk_version is None:
1128 if not whole_file:
1129 min_sdk_version = GetMinSdkVersionInt(
1130 input_name, codename_to_api_level_map)
1131 if min_sdk_version is not None:
1132 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1133
T.R. Fullhart37e10522013-03-18 10:31:26 -07001134 cmd.extend([key + OPTIONS.public_key_suffix,
1135 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001136 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001137
Tao Bao73dd4f42018-10-04 16:25:33 -07001138 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001139 if password is not None:
1140 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001141 stdoutdata, _ = proc.communicate(password)
1142 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001143 raise ExternalError(
1144 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001145 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001146
Doug Zongkereef39442009-04-02 12:14:19 -07001147
Doug Zongker37974732010-09-16 17:44:38 -07001148def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001149 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001150
Tao Bao9dd909e2017-11-14 11:27:32 -08001151 For non-AVB images, raise exception if the data is too big. Print a warning
1152 if the data is nearing the maximum size.
1153
1154 For AVB images, the actual image size should be identical to the limit.
1155
1156 Args:
1157 data: A string that contains all the data for the partition.
1158 target: The partition name. The ".img" suffix is optional.
1159 info_dict: The dict to be looked up for relevant info.
1160 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001161 if target.endswith(".img"):
1162 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001163 mount_point = "/" + target
1164
Ying Wangf8824af2014-06-03 14:07:27 -07001165 fs_type = None
1166 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001167 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001168 if mount_point == "/userdata":
1169 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001170 p = info_dict["fstab"][mount_point]
1171 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001172 device = p.device
1173 if "/" in device:
1174 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001175 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001176 if not fs_type or not limit:
1177 return
Doug Zongkereef39442009-04-02 12:14:19 -07001178
Andrew Boie0f9aec82012-02-14 09:32:52 -08001179 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001180 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1181 # path.
1182 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1183 if size != limit:
1184 raise ExternalError(
1185 "Mismatching image size for %s: expected %d actual %d" % (
1186 target, limit, size))
1187 else:
1188 pct = float(size) * 100.0 / limit
1189 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1190 if pct >= 99.0:
1191 raise ExternalError(msg)
1192 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001193 logger.warning("\n WARNING: %s\n", msg)
1194 else:
1195 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001196
1197
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001198def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001199 """Parses the APK certs info from a given target-files zip.
1200
1201 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1202 tuple with the following elements: (1) a dictionary that maps packages to
1203 certs (based on the "certificate" and "private_key" attributes in the file;
1204 (2) a string representing the extension of compressed APKs in the target files
1205 (e.g ".gz", ".bro").
1206
1207 Args:
1208 tf_zip: The input target_files ZipFile (already open).
1209
1210 Returns:
1211 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1212 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1213 no compressed APKs.
1214 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001215 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001216 compressed_extension = None
1217
Tao Bao0f990332017-09-08 19:02:54 -07001218 # META/apkcerts.txt contains the info for _all_ the packages known at build
1219 # time. Filter out the ones that are not installed.
1220 installed_files = set()
1221 for name in tf_zip.namelist():
1222 basename = os.path.basename(name)
1223 if basename:
1224 installed_files.add(basename)
1225
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001226 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1227 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001228 if not line:
1229 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001230 m = re.match(
1231 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1232 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1233 line)
1234 if not m:
1235 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001236
Tao Bao818ddf52018-01-05 11:17:34 -08001237 matches = m.groupdict()
1238 cert = matches["CERT"]
1239 privkey = matches["PRIVKEY"]
1240 name = matches["NAME"]
1241 this_compressed_extension = matches["COMPRESSED"]
1242
1243 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1244 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1245 if cert in SPECIAL_CERT_STRINGS and not privkey:
1246 certmap[name] = cert
1247 elif (cert.endswith(OPTIONS.public_key_suffix) and
1248 privkey.endswith(OPTIONS.private_key_suffix) and
1249 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1250 certmap[name] = cert[:-public_key_suffix_len]
1251 else:
1252 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1253
1254 if not this_compressed_extension:
1255 continue
1256
1257 # Only count the installed files.
1258 filename = name + '.' + this_compressed_extension
1259 if filename not in installed_files:
1260 continue
1261
1262 # Make sure that all the values in the compression map have the same
1263 # extension. We don't support multiple compression methods in the same
1264 # system image.
1265 if compressed_extension:
1266 if this_compressed_extension != compressed_extension:
1267 raise ValueError(
1268 "Multiple compressed extensions: {} vs {}".format(
1269 compressed_extension, this_compressed_extension))
1270 else:
1271 compressed_extension = this_compressed_extension
1272
1273 return (certmap,
1274 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001275
1276
Doug Zongkereef39442009-04-02 12:14:19 -07001277COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001278Global options
1279
1280 -p (--path) <dir>
1281 Prepend <dir>/bin to the list of places to search for binaries run by this
1282 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001283
Doug Zongker05d3dea2009-06-22 11:32:31 -07001284 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001285 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001286
Tao Bao30df8b42018-04-23 15:32:53 -07001287 -x (--extra) <key=value>
1288 Add a key/value pair to the 'extras' dict, which device-specific extension
1289 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001290
Doug Zongkereef39442009-04-02 12:14:19 -07001291 -v (--verbose)
1292 Show command lines being executed.
1293
1294 -h (--help)
1295 Display this usage message and exit.
1296"""
1297
1298def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001299 print(docstring.rstrip("\n"))
1300 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001301
1302
1303def ParseOptions(argv,
1304 docstring,
1305 extra_opts="", extra_long_opts=(),
1306 extra_option_handler=None):
1307 """Parse the options in argv and return any arguments that aren't
1308 flags. docstring is the calling module's docstring, to be displayed
1309 for errors and -h. extra_opts and extra_long_opts are for flags
1310 defined by the caller, which are processed by passing them to
1311 extra_option_handler."""
1312
1313 try:
1314 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001315 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001316 ["help", "verbose", "path=", "signapk_path=",
1317 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001318 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001319 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1320 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001321 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001322 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001323 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001324 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001325 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001326 sys.exit(2)
1327
Doug Zongkereef39442009-04-02 12:14:19 -07001328 for o, a in opts:
1329 if o in ("-h", "--help"):
1330 Usage(docstring)
1331 sys.exit()
1332 elif o in ("-v", "--verbose"):
1333 OPTIONS.verbose = True
1334 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001335 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001336 elif o in ("--signapk_path",):
1337 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001338 elif o in ("--signapk_shared_library_path",):
1339 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001340 elif o in ("--extra_signapk_args",):
1341 OPTIONS.extra_signapk_args = shlex.split(a)
1342 elif o in ("--java_path",):
1343 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001344 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001345 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001346 elif o in ("--public_key_suffix",):
1347 OPTIONS.public_key_suffix = a
1348 elif o in ("--private_key_suffix",):
1349 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001350 elif o in ("--boot_signer_path",):
1351 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001352 elif o in ("--boot_signer_args",):
1353 OPTIONS.boot_signer_args = shlex.split(a)
1354 elif o in ("--verity_signer_path",):
1355 OPTIONS.verity_signer_path = a
1356 elif o in ("--verity_signer_args",):
1357 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001358 elif o in ("-s", "--device_specific"):
1359 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001360 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001361 key, value = a.split("=", 1)
1362 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001363 else:
1364 if extra_option_handler is None or not extra_option_handler(o, a):
1365 assert False, "unknown option \"%s\"" % (o,)
1366
Doug Zongker85448772014-09-09 14:59:20 -07001367 if OPTIONS.search_path:
1368 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1369 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001370
1371 return args
1372
1373
Tao Bao4c851b12016-09-19 13:54:38 -07001374def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001375 """Make a temp file and add it to the list of things to be deleted
1376 when Cleanup() is called. Return the filename."""
1377 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1378 os.close(fd)
1379 OPTIONS.tempfiles.append(fn)
1380 return fn
1381
1382
Tao Bao1c830bf2017-12-25 10:43:47 -08001383def MakeTempDir(prefix='tmp', suffix=''):
1384 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1385
1386 Returns:
1387 The absolute pathname of the new directory.
1388 """
1389 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1390 OPTIONS.tempfiles.append(dir_name)
1391 return dir_name
1392
1393
Doug Zongkereef39442009-04-02 12:14:19 -07001394def Cleanup():
1395 for i in OPTIONS.tempfiles:
1396 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001397 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001398 else:
1399 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001400 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001401
1402
1403class PasswordManager(object):
1404 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001405 self.editor = os.getenv("EDITOR")
1406 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001407
1408 def GetPasswords(self, items):
1409 """Get passwords corresponding to each string in 'items',
1410 returning a dict. (The dict may have keys in addition to the
1411 values in 'items'.)
1412
1413 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1414 user edit that file to add more needed passwords. If no editor is
1415 available, or $ANDROID_PW_FILE isn't define, prompts the user
1416 interactively in the ordinary way.
1417 """
1418
1419 current = self.ReadFile()
1420
1421 first = True
1422 while True:
1423 missing = []
1424 for i in items:
1425 if i not in current or not current[i]:
1426 missing.append(i)
1427 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001428 if not missing:
1429 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001430
1431 for i in missing:
1432 current[i] = ""
1433
1434 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001435 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001436 answer = raw_input("try to edit again? [y]> ").strip()
1437 if answer and answer[0] not in 'yY':
1438 raise RuntimeError("key passwords unavailable")
1439 first = False
1440
1441 current = self.UpdateAndReadFile(current)
1442
Dan Albert8b72aef2015-03-23 19:13:21 -07001443 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001444 """Prompt the user to enter a value (password) for each key in
1445 'current' whose value is fales. Returns a new dict with all the
1446 values.
1447 """
1448 result = {}
1449 for k, v in sorted(current.iteritems()):
1450 if v:
1451 result[k] = v
1452 else:
1453 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001454 result[k] = getpass.getpass(
1455 "Enter password for %s key> " % k).strip()
1456 if result[k]:
1457 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001458 return result
1459
1460 def UpdateAndReadFile(self, current):
1461 if not self.editor or not self.pwfile:
1462 return self.PromptResult(current)
1463
1464 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001465 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001466 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1467 f.write("# (Additional spaces are harmless.)\n\n")
1468
1469 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001470 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1471 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001472 f.write("[[[ %s ]]] %s\n" % (v, k))
1473 if not v and first_line is None:
1474 # position cursor on first line with no password.
1475 first_line = i + 4
1476 f.close()
1477
Tao Bao986ee862018-10-04 15:46:16 -07001478 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001479
1480 return self.ReadFile()
1481
1482 def ReadFile(self):
1483 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001484 if self.pwfile is None:
1485 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001486 try:
1487 f = open(self.pwfile, "r")
1488 for line in f:
1489 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001490 if not line or line[0] == '#':
1491 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001492 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1493 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001494 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001495 else:
1496 result[m.group(2)] = m.group(1)
1497 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001498 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001499 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001500 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001501 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001502
1503
Dan Albert8e0178d2015-01-27 15:53:15 -08001504def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1505 compress_type=None):
1506 import datetime
1507
1508 # http://b/18015246
1509 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1510 # for files larger than 2GiB. We can work around this by adjusting their
1511 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1512 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1513 # it isn't clear to me exactly what circumstances cause this).
1514 # `zipfile.write()` must be used directly to work around this.
1515 #
1516 # This mess can be avoided if we port to python3.
1517 saved_zip64_limit = zipfile.ZIP64_LIMIT
1518 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1519
1520 if compress_type is None:
1521 compress_type = zip_file.compression
1522 if arcname is None:
1523 arcname = filename
1524
1525 saved_stat = os.stat(filename)
1526
1527 try:
1528 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1529 # file to be zipped and reset it when we're done.
1530 os.chmod(filename, perms)
1531
1532 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001533 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1534 # intentional. zip stores datetimes in local time without a time zone
1535 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1536 # in the zip archive.
1537 local_epoch = datetime.datetime.fromtimestamp(0)
1538 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001539 os.utime(filename, (timestamp, timestamp))
1540
1541 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1542 finally:
1543 os.chmod(filename, saved_stat.st_mode)
1544 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1545 zipfile.ZIP64_LIMIT = saved_zip64_limit
1546
1547
Tao Bao58c1b962015-05-20 09:32:18 -07001548def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001549 compress_type=None):
1550 """Wrap zipfile.writestr() function to work around the zip64 limit.
1551
1552 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1553 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1554 when calling crc32(bytes).
1555
1556 But it still works fine to write a shorter string into a large zip file.
1557 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1558 when we know the string won't be too long.
1559 """
1560
1561 saved_zip64_limit = zipfile.ZIP64_LIMIT
1562 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1563
1564 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1565 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001566 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001567 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001568 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001569 else:
Tao Baof3282b42015-04-01 11:21:55 -07001570 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001571 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1572 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1573 # such a case (since
1574 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1575 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1576 # permission bits. We follow the logic in Python 3 to get consistent
1577 # behavior between using the two versions.
1578 if not zinfo.external_attr:
1579 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001580
1581 # If compress_type is given, it overrides the value in zinfo.
1582 if compress_type is not None:
1583 zinfo.compress_type = compress_type
1584
Tao Bao58c1b962015-05-20 09:32:18 -07001585 # If perms is given, it has a priority.
1586 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001587 # If perms doesn't set the file type, mark it as a regular file.
1588 if perms & 0o770000 == 0:
1589 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001590 zinfo.external_attr = perms << 16
1591
Tao Baof3282b42015-04-01 11:21:55 -07001592 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001593 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1594
Dan Albert8b72aef2015-03-23 19:13:21 -07001595 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001596 zipfile.ZIP64_LIMIT = saved_zip64_limit
1597
1598
Tao Bao89d7ab22017-12-14 17:05:33 -08001599def ZipDelete(zip_filename, entries):
1600 """Deletes entries from a ZIP file.
1601
1602 Since deleting entries from a ZIP file is not supported, it shells out to
1603 'zip -d'.
1604
1605 Args:
1606 zip_filename: The name of the ZIP file.
1607 entries: The name of the entry, or the list of names to be deleted.
1608
1609 Raises:
1610 AssertionError: In case of non-zero return from 'zip'.
1611 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001612 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001613 entries = [entries]
1614 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001615 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001616
1617
Tao Baof3282b42015-04-01 11:21:55 -07001618def ZipClose(zip_file):
1619 # http://b/18015246
1620 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1621 # central directory.
1622 saved_zip64_limit = zipfile.ZIP64_LIMIT
1623 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1624
1625 zip_file.close()
1626
1627 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001628
1629
1630class DeviceSpecificParams(object):
1631 module = None
1632 def __init__(self, **kwargs):
1633 """Keyword arguments to the constructor become attributes of this
1634 object, which is passed to all functions in the device-specific
1635 module."""
1636 for k, v in kwargs.iteritems():
1637 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001638 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001639
1640 if self.module is None:
1641 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001642 if not path:
1643 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001644 try:
1645 if os.path.isdir(path):
1646 info = imp.find_module("releasetools", [path])
1647 else:
1648 d, f = os.path.split(path)
1649 b, x = os.path.splitext(f)
1650 if x == ".py":
1651 f = b
1652 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001653 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001654 self.module = imp.load_module("device_specific", *info)
1655 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001656 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001657
1658 def _DoCall(self, function_name, *args, **kwargs):
1659 """Call the named function in the device-specific module, passing
1660 the given args and kwargs. The first argument to the call will be
1661 the DeviceSpecific object itself. If there is no module, or the
1662 module does not define the function, return the value of the
1663 'default' kwarg (which itself defaults to None)."""
1664 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001665 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001666 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1667
1668 def FullOTA_Assertions(self):
1669 """Called after emitting the block of assertions at the top of a
1670 full OTA package. Implementations can add whatever additional
1671 assertions they like."""
1672 return self._DoCall("FullOTA_Assertions")
1673
Doug Zongkere5ff5902012-01-17 10:55:37 -08001674 def FullOTA_InstallBegin(self):
1675 """Called at the start of full OTA installation."""
1676 return self._DoCall("FullOTA_InstallBegin")
1677
Yifan Hong10c530d2018-12-27 17:34:18 -08001678 def FullOTA_GetBlockDifferences(self):
1679 """Called during full OTA installation and verification.
1680 Implementation should return a list of BlockDifference objects describing
1681 the update on each additional partitions.
1682 """
1683 return self._DoCall("FullOTA_GetBlockDifferences")
1684
Doug Zongker05d3dea2009-06-22 11:32:31 -07001685 def FullOTA_InstallEnd(self):
1686 """Called at the end of full OTA installation; typically this is
1687 used to install the image for the device's baseband processor."""
1688 return self._DoCall("FullOTA_InstallEnd")
1689
1690 def IncrementalOTA_Assertions(self):
1691 """Called after emitting the block of assertions at the top of an
1692 incremental OTA package. Implementations can add whatever
1693 additional assertions they like."""
1694 return self._DoCall("IncrementalOTA_Assertions")
1695
Doug Zongkere5ff5902012-01-17 10:55:37 -08001696 def IncrementalOTA_VerifyBegin(self):
1697 """Called at the start of the verification phase of incremental
1698 OTA installation; additional checks can be placed here to abort
1699 the script before any changes are made."""
1700 return self._DoCall("IncrementalOTA_VerifyBegin")
1701
Doug Zongker05d3dea2009-06-22 11:32:31 -07001702 def IncrementalOTA_VerifyEnd(self):
1703 """Called at the end of the verification phase of incremental OTA
1704 installation; additional checks can be placed here to abort the
1705 script before any changes are made."""
1706 return self._DoCall("IncrementalOTA_VerifyEnd")
1707
Doug Zongkere5ff5902012-01-17 10:55:37 -08001708 def IncrementalOTA_InstallBegin(self):
1709 """Called at the start of incremental OTA installation (after
1710 verification is complete)."""
1711 return self._DoCall("IncrementalOTA_InstallBegin")
1712
Yifan Hong10c530d2018-12-27 17:34:18 -08001713 def IncrementalOTA_GetBlockDifferences(self):
1714 """Called during incremental OTA installation and verification.
1715 Implementation should return a list of BlockDifference objects describing
1716 the update on each additional partitions.
1717 """
1718 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1719
Doug Zongker05d3dea2009-06-22 11:32:31 -07001720 def IncrementalOTA_InstallEnd(self):
1721 """Called at the end of incremental OTA installation; typically
1722 this is used to install the image for the device's baseband
1723 processor."""
1724 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001725
Tao Bao9bc6bb22015-11-09 16:58:28 -08001726 def VerifyOTA_Assertions(self):
1727 return self._DoCall("VerifyOTA_Assertions")
1728
Tao Bao76def242017-11-21 09:25:31 -08001729
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001730class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001731 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001732 self.name = name
1733 self.data = data
1734 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001735 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001736 self.sha1 = sha1(data).hexdigest()
1737
1738 @classmethod
1739 def FromLocalFile(cls, name, diskname):
1740 f = open(diskname, "rb")
1741 data = f.read()
1742 f.close()
1743 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001744
1745 def WriteToTemp(self):
1746 t = tempfile.NamedTemporaryFile()
1747 t.write(self.data)
1748 t.flush()
1749 return t
1750
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001751 def WriteToDir(self, d):
1752 with open(os.path.join(d, self.name), "wb") as fp:
1753 fp.write(self.data)
1754
Geremy Condra36bd3652014-02-06 19:45:10 -08001755 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001756 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001757
Tao Bao76def242017-11-21 09:25:31 -08001758
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001759DIFF_PROGRAM_BY_EXT = {
1760 ".gz" : "imgdiff",
1761 ".zip" : ["imgdiff", "-z"],
1762 ".jar" : ["imgdiff", "-z"],
1763 ".apk" : ["imgdiff", "-z"],
1764 ".img" : "imgdiff",
1765 }
1766
Tao Bao76def242017-11-21 09:25:31 -08001767
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001768class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001769 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001770 self.tf = tf
1771 self.sf = sf
1772 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001773 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001774
1775 def ComputePatch(self):
1776 """Compute the patch (as a string of data) needed to turn sf into
1777 tf. Returns the same tuple as GetPatch()."""
1778
1779 tf = self.tf
1780 sf = self.sf
1781
Doug Zongker24cd2802012-08-14 16:36:15 -07001782 if self.diff_program:
1783 diff_program = self.diff_program
1784 else:
1785 ext = os.path.splitext(tf.name)[1]
1786 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001787
1788 ttemp = tf.WriteToTemp()
1789 stemp = sf.WriteToTemp()
1790
1791 ext = os.path.splitext(tf.name)[1]
1792
1793 try:
1794 ptemp = tempfile.NamedTemporaryFile()
1795 if isinstance(diff_program, list):
1796 cmd = copy.copy(diff_program)
1797 else:
1798 cmd = [diff_program]
1799 cmd.append(stemp.name)
1800 cmd.append(ttemp.name)
1801 cmd.append(ptemp.name)
1802 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001803 err = []
1804 def run():
1805 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001806 if e:
1807 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001808 th = threading.Thread(target=run)
1809 th.start()
1810 th.join(timeout=300) # 5 mins
1811 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001812 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001813 p.terminate()
1814 th.join(5)
1815 if th.is_alive():
1816 p.kill()
1817 th.join()
1818
Tianjie Xua2a9f992018-01-05 15:15:54 -08001819 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001820 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001821 self.patch = None
1822 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001823 diff = ptemp.read()
1824 finally:
1825 ptemp.close()
1826 stemp.close()
1827 ttemp.close()
1828
1829 self.patch = diff
1830 return self.tf, self.sf, self.patch
1831
1832
1833 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001834 """Returns a tuple of (target_file, source_file, patch_data).
1835
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001836 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001837 computing the patch failed.
1838 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001839 return self.tf, self.sf, self.patch
1840
1841
1842def ComputeDifferences(diffs):
1843 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001844 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001845
1846 # Do the largest files first, to try and reduce the long-pole effect.
1847 by_size = [(i.tf.size, i) for i in diffs]
1848 by_size.sort(reverse=True)
1849 by_size = [i[1] for i in by_size]
1850
1851 lock = threading.Lock()
1852 diff_iter = iter(by_size) # accessed under lock
1853
1854 def worker():
1855 try:
1856 lock.acquire()
1857 for d in diff_iter:
1858 lock.release()
1859 start = time.time()
1860 d.ComputePatch()
1861 dur = time.time() - start
1862 lock.acquire()
1863
1864 tf, sf, patch = d.GetPatch()
1865 if sf.name == tf.name:
1866 name = tf.name
1867 else:
1868 name = "%s (%s)" % (tf.name, sf.name)
1869 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001870 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001871 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001872 logger.info(
1873 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1874 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001875 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001876 except Exception:
1877 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001878 raise
1879
1880 # start worker threads; wait for them all to finish.
1881 threads = [threading.Thread(target=worker)
1882 for i in range(OPTIONS.worker_threads)]
1883 for th in threads:
1884 th.start()
1885 while threads:
1886 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001887
1888
Dan Albert8b72aef2015-03-23 19:13:21 -07001889class BlockDifference(object):
1890 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001891 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001892 self.tgt = tgt
1893 self.src = src
1894 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001895 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001896 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001897
Tao Baodd2a5892015-03-12 12:32:37 -07001898 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001899 version = max(
1900 int(i) for i in
1901 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001902 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001903 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001904
1905 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001906 version=self.version,
1907 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001908 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001909 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001910 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001911 self.touched_src_ranges = b.touched_src_ranges
1912 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001913
Yifan Hong10c530d2018-12-27 17:34:18 -08001914 # On devices with dynamic partitions, for new partitions,
1915 # src is None but OPTIONS.source_info_dict is not.
1916 if OPTIONS.source_info_dict is None:
1917 is_dynamic_build = OPTIONS.info_dict.get(
1918 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001919 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001920 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001921 is_dynamic_build = OPTIONS.source_info_dict.get(
1922 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001923 is_dynamic_source = partition in shlex.split(
1924 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001925
Yifan Hongbb2658d2019-01-25 12:30:58 -08001926 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001927 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1928
Yifan Hongbb2658d2019-01-25 12:30:58 -08001929 # For dynamic partitions builds, check partition list in both source
1930 # and target build because new partitions may be added, and existing
1931 # partitions may be removed.
1932 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1933
Yifan Hong10c530d2018-12-27 17:34:18 -08001934 if is_dynamic:
1935 self.device = 'map_partition("%s")' % partition
1936 else:
1937 if OPTIONS.source_info_dict is None:
1938 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1939 else:
1940 _, device_path = GetTypeAndDevice("/" + partition,
1941 OPTIONS.source_info_dict)
1942 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001943
Tao Baod8d14be2016-02-04 14:26:02 -08001944 @property
1945 def required_cache(self):
1946 return self._required_cache
1947
Tao Bao76def242017-11-21 09:25:31 -08001948 def WriteScript(self, script, output_zip, progress=None,
1949 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001950 if not self.src:
1951 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001952 script.Print("Patching %s image unconditionally..." % (self.partition,))
1953 else:
1954 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001955
Dan Albert8b72aef2015-03-23 19:13:21 -07001956 if progress:
1957 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001958 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001959
1960 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001961 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001962
Tao Bao9bc6bb22015-11-09 16:58:28 -08001963 def WriteStrictVerifyScript(self, script):
1964 """Verify all the blocks in the care_map, including clobbered blocks.
1965
1966 This differs from the WriteVerifyScript() function: a) it prints different
1967 error messages; b) it doesn't allow half-way updated images to pass the
1968 verification."""
1969
1970 partition = self.partition
1971 script.Print("Verifying %s..." % (partition,))
1972 ranges = self.tgt.care_map
1973 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001974 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001975 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1976 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001977 self.device, ranges_str,
1978 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001979 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001980 script.AppendExtra("")
1981
Tao Baod522bdc2016-04-12 15:53:16 -07001982 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001983 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001984
1985 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001986 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001987 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001988
1989 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001990 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001991 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001992 ranges = self.touched_src_ranges
1993 expected_sha1 = self.touched_src_sha1
1994 else:
1995 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1996 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001997
1998 # No blocks to be checked, skipping.
1999 if not ranges:
2000 return
2001
Tao Bao5ece99d2015-05-12 11:42:31 -07002002 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002003 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002004 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002005 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2006 '"%s.patch.dat")) then' % (
2007 self.device, ranges_str, expected_sha1,
2008 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002009 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002010 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002011
Tianjie Xufc3422a2015-12-15 11:53:59 -08002012 if self.version >= 4:
2013
2014 # Bug: 21124327
2015 # When generating incrementals for the system and vendor partitions in
2016 # version 4 or newer, explicitly check the first block (which contains
2017 # the superblock) of the partition to see if it's what we expect. If
2018 # this check fails, give an explicit log message about the partition
2019 # having been remounted R/W (the most likely explanation).
2020 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002021 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002022
2023 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002024 if partition == "system":
2025 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2026 else:
2027 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002028 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002029 'ifelse (block_image_recover({device}, "{ranges}") && '
2030 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002031 'package_extract_file("{partition}.transfer.list"), '
2032 '"{partition}.new.dat", "{partition}.patch.dat"), '
2033 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002034 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002035 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002036 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002037
Tao Baodd2a5892015-03-12 12:32:37 -07002038 # Abort the OTA update. Note that the incremental OTA cannot be applied
2039 # even if it may match the checksum of the target partition.
2040 # a) If version < 3, operations like move and erase will make changes
2041 # unconditionally and damage the partition.
2042 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002043 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002044 if partition == "system":
2045 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2046 else:
2047 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2048 script.AppendExtra((
2049 'abort("E%d: %s partition has unexpected contents");\n'
2050 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002051
Yifan Hong10c530d2018-12-27 17:34:18 -08002052 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002053 partition = self.partition
2054 script.Print('Verifying the updated %s image...' % (partition,))
2055 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2056 ranges = self.tgt.care_map
2057 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002058 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002059 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002060 self.device, ranges_str,
2061 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002062
2063 # Bug: 20881595
2064 # Verify that extended blocks are really zeroed out.
2065 if self.tgt.extended:
2066 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002067 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002068 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002069 self.device, ranges_str,
2070 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002071 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002072 if partition == "system":
2073 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2074 else:
2075 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002076 script.AppendExtra(
2077 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002078 ' abort("E%d: %s partition has unexpected non-zero contents after '
2079 'OTA update");\n'
2080 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002081 else:
2082 script.Print('Verified the updated %s image.' % (partition,))
2083
Tianjie Xu209db462016-05-24 17:34:52 -07002084 if partition == "system":
2085 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2086 else:
2087 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2088
Tao Bao5fcaaef2015-06-01 13:40:49 -07002089 script.AppendExtra(
2090 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002091 ' abort("E%d: %s partition has unexpected contents after OTA '
2092 'update");\n'
2093 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002094
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002095 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002096 ZipWrite(output_zip,
2097 '{}.transfer.list'.format(self.path),
2098 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002099
Tao Bao76def242017-11-21 09:25:31 -08002100 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2101 # its size. Quailty 9 almost triples the compression time but doesn't
2102 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002103 # zip | brotli(quality 6) | brotli(quality 9)
2104 # compressed_size: 942M | 869M (~8% reduced) | 854M
2105 # compression_time: 75s | 265s | 719s
2106 # decompression_time: 15s | 25s | 25s
2107
2108 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002109 brotli_cmd = ['brotli', '--quality=6',
2110 '--output={}.new.dat.br'.format(self.path),
2111 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002112 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002113 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002114
2115 new_data_name = '{}.new.dat.br'.format(self.partition)
2116 ZipWrite(output_zip,
2117 '{}.new.dat.br'.format(self.path),
2118 new_data_name,
2119 compress_type=zipfile.ZIP_STORED)
2120 else:
2121 new_data_name = '{}.new.dat'.format(self.partition)
2122 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2123
Dan Albert8e0178d2015-01-27 15:53:15 -08002124 ZipWrite(output_zip,
2125 '{}.patch.dat'.format(self.path),
2126 '{}.patch.dat'.format(self.partition),
2127 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002128
Tianjie Xu209db462016-05-24 17:34:52 -07002129 if self.partition == "system":
2130 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2131 else:
2132 code = ErrorCode.VENDOR_UPDATE_FAILURE
2133
Yifan Hong10c530d2018-12-27 17:34:18 -08002134 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002135 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002136 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002137 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002138 device=self.device, partition=self.partition,
2139 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002140 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002141
Dan Albert8b72aef2015-03-23 19:13:21 -07002142 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002143 data = source.ReadRangeSet(ranges)
2144 ctx = sha1()
2145
2146 for p in data:
2147 ctx.update(p)
2148
2149 return ctx.hexdigest()
2150
Tao Baoe9b61912015-07-09 17:37:49 -07002151 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2152 """Return the hash value for all zero blocks."""
2153 zero_block = '\x00' * 4096
2154 ctx = sha1()
2155 for _ in range(num_blocks):
2156 ctx.update(zero_block)
2157
2158 return ctx.hexdigest()
2159
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002160
2161DataImage = blockimgdiff.DataImage
Yifan Hong8a66a712019-04-04 15:37:57 -07002162EmptyImage = blockimgdiff.EmptyImage
Tao Bao76def242017-11-21 09:25:31 -08002163
Doug Zongker96a57e72010-09-26 14:57:41 -07002164# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002165PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002166 "ext4": "EMMC",
2167 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002168 "f2fs": "EMMC",
2169 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002170}
Doug Zongker96a57e72010-09-26 14:57:41 -07002171
Tao Bao76def242017-11-21 09:25:31 -08002172
Doug Zongker96a57e72010-09-26 14:57:41 -07002173def GetTypeAndDevice(mount_point, info):
2174 fstab = info["fstab"]
2175 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002176 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2177 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002178 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002179 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002180
2181
2182def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002183 """Parses and converts a PEM-encoded certificate into DER-encoded.
2184
2185 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2186
2187 Returns:
2188 The decoded certificate string.
2189 """
2190 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002191 save = False
2192 for line in data.split("\n"):
2193 if "--END CERTIFICATE--" in line:
2194 break
2195 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002196 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002197 if "--BEGIN CERTIFICATE--" in line:
2198 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002199 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002200 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002201
Tao Bao04e1f012018-02-04 12:13:35 -08002202
2203def ExtractPublicKey(cert):
2204 """Extracts the public key (PEM-encoded) from the given certificate file.
2205
2206 Args:
2207 cert: The certificate filename.
2208
2209 Returns:
2210 The public key string.
2211
2212 Raises:
2213 AssertionError: On non-zero return from 'openssl'.
2214 """
2215 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2216 # While openssl 1.1 writes the key into the given filename followed by '-out',
2217 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2218 # stdout instead.
2219 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2220 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2221 pubkey, stderrdata = proc.communicate()
2222 assert proc.returncode == 0, \
2223 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2224 return pubkey
2225
2226
Tao Bao2cc0ca12019-03-15 10:44:43 -07002227def ExtractAvbPublicKey(key):
2228 """Extracts the AVB public key from the given public or private key.
2229
2230 Args:
2231 key: The input key file, which should be PEM-encoded public or private key.
2232
2233 Returns:
2234 The path to the extracted AVB public key file.
2235 """
2236 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2237 RunAndCheckOutput(
2238 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2239 return output
2240
2241
Doug Zongker412c02f2014-02-13 10:58:24 -08002242def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2243 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002244 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002245
Tao Bao6d5d6232018-03-09 17:04:42 -08002246 Most of the space in the boot and recovery images is just the kernel, which is
2247 identical for the two, so the resulting patch should be efficient. Add it to
2248 the output zip, along with a shell script that is run from init.rc on first
2249 boot to actually do the patching and install the new recovery image.
2250
2251 Args:
2252 input_dir: The top-level input directory of the target-files.zip.
2253 output_sink: The callback function that writes the result.
2254 recovery_img: File object for the recovery image.
2255 boot_img: File objects for the boot image.
2256 info_dict: A dict returned by common.LoadInfoDict() on the input
2257 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002258 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002259 if info_dict is None:
2260 info_dict = OPTIONS.info_dict
2261
Tao Bao6d5d6232018-03-09 17:04:42 -08002262 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002263
Tao Baof2cffbd2015-07-22 12:33:18 -07002264 if full_recovery_image:
2265 output_sink("etc/recovery.img", recovery_img.data)
2266
2267 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002268 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002269 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002270 # With system-root-image, boot and recovery images will have mismatching
2271 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2272 # to handle such a case.
2273 if system_root_image:
2274 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002275 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002276 assert not os.path.exists(path)
2277 else:
2278 diff_program = ["imgdiff"]
2279 if os.path.exists(path):
2280 diff_program.append("-b")
2281 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002282 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002283 else:
2284 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002285
2286 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2287 _, _, patch = d.ComputePatch()
2288 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002289
Dan Albertebb19aa2015-03-27 19:11:53 -07002290 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002291 # The following GetTypeAndDevice()s need to use the path in the target
2292 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002293 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2294 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2295 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002296 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002297
Tao Baof2cffbd2015-07-22 12:33:18 -07002298 if full_recovery_image:
2299 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002300if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2301 applypatch \\
2302 --flash /system/etc/recovery.img \\
2303 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2304 log -t recovery "Installing new recovery image: succeeded" || \\
2305 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002306else
2307 log -t recovery "Recovery image already installed"
2308fi
2309""" % {'type': recovery_type,
2310 'device': recovery_device,
2311 'sha1': recovery_img.sha1,
2312 'size': recovery_img.size}
2313 else:
2314 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002315if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2316 applypatch %(bonus_args)s \\
2317 --patch /system/recovery-from-boot.p \\
2318 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2319 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2320 log -t recovery "Installing new recovery image: succeeded" || \\
2321 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002322else
2323 log -t recovery "Recovery image already installed"
2324fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002325""" % {'boot_size': boot_img.size,
2326 'boot_sha1': boot_img.sha1,
2327 'recovery_size': recovery_img.size,
2328 'recovery_sha1': recovery_img.sha1,
2329 'boot_type': boot_type,
2330 'boot_device': boot_device,
2331 'recovery_type': recovery_type,
2332 'recovery_device': recovery_device,
2333 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002334
2335 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002336 # in the L release.
2337 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002338
Tao Bao32fcdab2018-10-12 10:30:39 -07002339 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002340
2341 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002342
2343
2344class DynamicPartitionUpdate(object):
2345 def __init__(self, src_group=None, tgt_group=None, progress=None,
2346 block_difference=None):
2347 self.src_group = src_group
2348 self.tgt_group = tgt_group
2349 self.progress = progress
2350 self.block_difference = block_difference
2351
2352 @property
2353 def src_size(self):
2354 if not self.block_difference:
2355 return 0
2356 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2357
2358 @property
2359 def tgt_size(self):
2360 if not self.block_difference:
2361 return 0
2362 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2363
2364 @staticmethod
2365 def _GetSparseImageSize(img):
2366 if not img:
2367 return 0
2368 return img.blocksize * img.total_blocks
2369
2370
2371class DynamicGroupUpdate(object):
2372 def __init__(self, src_size=None, tgt_size=None):
2373 # None: group does not exist. 0: no size limits.
2374 self.src_size = src_size
2375 self.tgt_size = tgt_size
2376
2377
2378class DynamicPartitionsDifference(object):
2379 def __init__(self, info_dict, block_diffs, progress_dict=None,
2380 source_info_dict=None):
2381 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002382 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002383
2384 self._remove_all_before_apply = False
2385 if source_info_dict is None:
2386 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002387 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002388
Tao Baof1113e92019-06-18 12:10:14 -07002389 block_diff_dict = collections.OrderedDict(
2390 [(e.partition, e) for e in block_diffs])
2391
Yifan Hong10c530d2018-12-27 17:34:18 -08002392 assert len(block_diff_dict) == len(block_diffs), \
2393 "Duplicated BlockDifference object for {}".format(
2394 [partition for partition, count in
2395 collections.Counter(e.partition for e in block_diffs).items()
2396 if count > 1])
2397
Yifan Hong79997e52019-01-23 16:56:19 -08002398 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002399
2400 for p, block_diff in block_diff_dict.items():
2401 self._partition_updates[p] = DynamicPartitionUpdate()
2402 self._partition_updates[p].block_difference = block_diff
2403
2404 for p, progress in progress_dict.items():
2405 if p in self._partition_updates:
2406 self._partition_updates[p].progress = progress
2407
2408 tgt_groups = shlex.split(info_dict.get(
2409 "super_partition_groups", "").strip())
2410 src_groups = shlex.split(source_info_dict.get(
2411 "super_partition_groups", "").strip())
2412
2413 for g in tgt_groups:
2414 for p in shlex.split(info_dict.get(
2415 "super_%s_partition_list" % g, "").strip()):
2416 assert p in self._partition_updates, \
2417 "{} is in target super_{}_partition_list but no BlockDifference " \
2418 "object is provided.".format(p, g)
2419 self._partition_updates[p].tgt_group = g
2420
2421 for g in src_groups:
2422 for p in shlex.split(source_info_dict.get(
2423 "super_%s_partition_list" % g, "").strip()):
2424 assert p in self._partition_updates, \
2425 "{} is in source super_{}_partition_list but no BlockDifference " \
2426 "object is provided.".format(p, g)
2427 self._partition_updates[p].src_group = g
2428
Yifan Hong45433e42019-01-18 13:55:25 -08002429 target_dynamic_partitions = set(shlex.split(info_dict.get(
2430 "dynamic_partition_list", "").strip()))
2431 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2432 if u.tgt_size)
2433 assert block_diffs_with_target == target_dynamic_partitions, \
2434 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2435 list(target_dynamic_partitions), list(block_diffs_with_target))
2436
2437 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2438 "dynamic_partition_list", "").strip()))
2439 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2440 if u.src_size)
2441 assert block_diffs_with_source == source_dynamic_partitions, \
2442 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2443 list(source_dynamic_partitions), list(block_diffs_with_source))
2444
Yifan Hong10c530d2018-12-27 17:34:18 -08002445 if self._partition_updates:
2446 logger.info("Updating dynamic partitions %s",
2447 self._partition_updates.keys())
2448
Yifan Hong79997e52019-01-23 16:56:19 -08002449 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002450
2451 for g in tgt_groups:
2452 self._group_updates[g] = DynamicGroupUpdate()
2453 self._group_updates[g].tgt_size = int(info_dict.get(
2454 "super_%s_group_size" % g, "0").strip())
2455
2456 for g in src_groups:
2457 if g not in self._group_updates:
2458 self._group_updates[g] = DynamicGroupUpdate()
2459 self._group_updates[g].src_size = int(source_info_dict.get(
2460 "super_%s_group_size" % g, "0").strip())
2461
2462 self._Compute()
2463
2464 def WriteScript(self, script, output_zip, write_verify_script=False):
2465 script.Comment('--- Start patching dynamic partitions ---')
2466 for p, u in self._partition_updates.items():
2467 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2468 script.Comment('Patch partition %s' % p)
2469 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2470 write_verify_script=False)
2471
2472 op_list_path = MakeTempFile()
2473 with open(op_list_path, 'w') as f:
2474 for line in self._op_list:
2475 f.write('{}\n'.format(line))
2476
2477 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2478
2479 script.Comment('Update dynamic partition metadata')
2480 script.AppendExtra('assert(update_dynamic_partitions('
2481 'package_extract_file("dynamic_partitions_op_list")));')
2482
2483 if write_verify_script:
2484 for p, u in self._partition_updates.items():
2485 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2486 u.block_difference.WritePostInstallVerifyScript(script)
2487 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2488
2489 for p, u in self._partition_updates.items():
2490 if u.tgt_size and u.src_size <= u.tgt_size:
2491 script.Comment('Patch partition %s' % p)
2492 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2493 write_verify_script=write_verify_script)
2494 if write_verify_script:
2495 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2496
2497 script.Comment('--- End patching dynamic partitions ---')
2498
2499 def _Compute(self):
2500 self._op_list = list()
2501
2502 def append(line):
2503 self._op_list.append(line)
2504
2505 def comment(line):
2506 self._op_list.append("# %s" % line)
2507
2508 if self._remove_all_before_apply:
2509 comment('Remove all existing dynamic partitions and groups before '
2510 'applying full OTA')
2511 append('remove_all_groups')
2512
2513 for p, u in self._partition_updates.items():
2514 if u.src_group and not u.tgt_group:
2515 append('remove %s' % p)
2516
2517 for p, u in self._partition_updates.items():
2518 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2519 comment('Move partition %s from %s to default' % (p, u.src_group))
2520 append('move %s default' % p)
2521
2522 for p, u in self._partition_updates.items():
2523 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2524 comment('Shrink partition %s from %d to %d' %
2525 (p, u.src_size, u.tgt_size))
2526 append('resize %s %s' % (p, u.tgt_size))
2527
2528 for g, u in self._group_updates.items():
2529 if u.src_size is not None and u.tgt_size is None:
2530 append('remove_group %s' % g)
2531 if (u.src_size is not None and u.tgt_size is not None and
2532 u.src_size > u.tgt_size):
2533 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2534 append('resize_group %s %d' % (g, u.tgt_size))
2535
2536 for g, u in self._group_updates.items():
2537 if u.src_size is None and u.tgt_size is not None:
2538 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2539 append('add_group %s %d' % (g, u.tgt_size))
2540 if (u.src_size is not None and u.tgt_size is not None and
2541 u.src_size < u.tgt_size):
2542 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2543 append('resize_group %s %d' % (g, u.tgt_size))
2544
2545 for p, u in self._partition_updates.items():
2546 if u.tgt_group and not u.src_group:
2547 comment('Add partition %s to group %s' % (p, u.tgt_group))
2548 append('add %s %s' % (p, u.tgt_group))
2549
2550 for p, u in self._partition_updates.items():
2551 if u.tgt_size and u.src_size < u.tgt_size:
2552 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2553 append('resize %s %d' % (p, u.tgt_size))
2554
2555 for p, u in self._partition_updates.items():
2556 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2557 comment('Move partition %s from default to %s' %
2558 (p, u.tgt_group))
2559 append('move %s %s' % (p, u.tgt_group))