blob: b7a7f37cebee3cd1799b0f024fb8ca32c0dbeaf3 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070020import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070021import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070022import getopt
23import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010024import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070025import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070026import json
27import logging
28import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070029import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080030import platform
Doug Zongkereef39442009-04-02 12:14:19 -070031import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070032import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070033import shutil
34import subprocess
35import sys
36import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070037import threading
38import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070039import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080040from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070041
Tianjie Xu41976c72019-07-03 13:57:01 -070042import images
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070044from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070045
Tao Bao32fcdab2018-10-12 10:30:39 -070046logger = logging.getLogger(__name__)
47
Tao Bao986ee862018-10-04 15:46:16 -070048
Dan Albert8b72aef2015-03-23 19:13:21 -070049class Options(object):
50 def __init__(self):
Pavel Salomatov32676552019-03-06 20:00:45 +030051 base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
52 if base_out_path is None:
53 base_search_path = "out"
54 else:
Tao Bao2cc0ca12019-03-15 10:44:43 -070055 base_search_path = os.path.join(base_out_path,
56 os.path.basename(os.getcwd()))
Pavel Salomatov32676552019-03-06 20:00:45 +030057
Tao Baoa3705452019-06-24 15:33:41 -070058 # Python >= 3.3 returns 'linux', whereas Python 2.7 gives 'linux2'.
Dan Albert8b72aef2015-03-23 19:13:21 -070059 platform_search_path = {
Tao Baoa3705452019-06-24 15:33:41 -070060 "linux": os.path.join(base_search_path, "host/linux-x86"),
Pavel Salomatov32676552019-03-06 20:00:45 +030061 "linux2": os.path.join(base_search_path, "host/linux-x86"),
62 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070063 }
Doug Zongker85448772014-09-09 14:59:20 -070064
Tao Bao76def242017-11-21 09:25:31 -080065 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070066 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080067 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070068 self.extra_signapk_args = []
69 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080070 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.public_key_suffix = ".x509.pem"
72 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070073 # use otatools built boot_signer by default
74 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070075 self.boot_signer_args = []
76 self.verity_signer_path = None
77 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070078 self.verbose = False
79 self.tempfiles = []
80 self.device_specific = None
81 self.extras = {}
82 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070083 self.source_info_dict = None
84 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070085 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070086 # Stash size cannot exceed cache_size * threshold.
87 self.cache_size = None
88 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070089
90
91OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070092
Tao Bao71197512018-10-11 14:08:45 -070093# The block size that's used across the releasetools scripts.
94BLOCK_SIZE = 4096
95
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080096# Values for "certificate" in apkcerts that mean special things.
97SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
98
Tao Bao5cc0abb2019-03-21 10:18:05 -070099# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
100# that system_other is not in the list because we don't want to include its
101# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900102AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Steve Mucklee1b10862019-07-10 10:49:37 -0700103 'system_ext', 'vendor', 'vendor_boot')
Tao Bao9dd909e2017-11-14 11:27:32 -0800104
Tao Bao08c190f2019-06-03 23:07:58 -0700105# Chained VBMeta partitions.
106AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
107
Tianjie Xu861f4132018-09-12 11:49:33 -0700108# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900109PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700110
111
Tianjie Xu209db462016-05-24 17:34:52 -0700112class ErrorCode(object):
113 """Define error_codes for failures that happen during the actual
114 update package installation.
115
116 Error codes 0-999 are reserved for failures before the package
117 installation (i.e. low battery, package verification failure).
118 Detailed code in 'bootable/recovery/error_code.h' """
119
120 SYSTEM_VERIFICATION_FAILURE = 1000
121 SYSTEM_UPDATE_FAILURE = 1001
122 SYSTEM_UNEXPECTED_CONTENTS = 1002
123 SYSTEM_NONZERO_CONTENTS = 1003
124 SYSTEM_RECOVER_FAILURE = 1004
125 VENDOR_VERIFICATION_FAILURE = 2000
126 VENDOR_UPDATE_FAILURE = 2001
127 VENDOR_UNEXPECTED_CONTENTS = 2002
128 VENDOR_NONZERO_CONTENTS = 2003
129 VENDOR_RECOVER_FAILURE = 2004
130 OEM_PROP_MISMATCH = 3000
131 FINGERPRINT_MISMATCH = 3001
132 THUMBPRINT_MISMATCH = 3002
133 OLDER_BUILD = 3003
134 DEVICE_MISMATCH = 3004
135 BAD_PATCH_FILE = 3005
136 INSUFFICIENT_CACHE_SPACE = 3006
137 TUNE_PARTITION_FAILURE = 3007
138 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800139
Tao Bao80921982018-03-21 21:02:19 -0700140
Dan Albert8b72aef2015-03-23 19:13:21 -0700141class ExternalError(RuntimeError):
142 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700143
144
Tao Bao32fcdab2018-10-12 10:30:39 -0700145def InitLogging():
146 DEFAULT_LOGGING_CONFIG = {
147 'version': 1,
148 'disable_existing_loggers': False,
149 'formatters': {
150 'standard': {
151 'format':
152 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
153 'datefmt': '%Y-%m-%d %H:%M:%S',
154 },
155 },
156 'handlers': {
157 'default': {
158 'class': 'logging.StreamHandler',
159 'formatter': 'standard',
160 },
161 },
162 'loggers': {
163 '': {
164 'handlers': ['default'],
165 'level': 'WARNING',
166 'propagate': True,
167 }
168 }
169 }
170 env_config = os.getenv('LOGGING_CONFIG')
171 if env_config:
172 with open(env_config) as f:
173 config = json.load(f)
174 else:
175 config = DEFAULT_LOGGING_CONFIG
176
177 # Increase the logging level for verbose mode.
178 if OPTIONS.verbose:
179 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
180 config['loggers']['']['level'] = 'INFO'
181
182 logging.config.dictConfig(config)
183
184
Tao Bao39451582017-05-04 11:10:47 -0700185def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700186 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700187
Tao Bao73dd4f42018-10-04 16:25:33 -0700188 Args:
189 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700190 verbose: Whether the commands should be shown. Default to the global
191 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700192 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
193 stdin, etc. stdout and stderr will default to subprocess.PIPE and
194 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800195 universal_newlines will default to True, as most of the users in
196 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700197
198 Returns:
199 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700200 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700201 if 'stdout' not in kwargs and 'stderr' not in kwargs:
202 kwargs['stdout'] = subprocess.PIPE
203 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800204 if 'universal_newlines' not in kwargs:
205 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700206 # Don't log any if caller explicitly says so.
207 if verbose != False:
208 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700209 return subprocess.Popen(args, **kwargs)
210
211
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800212def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800213 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800214
215 Args:
216 args: The command represented as a list of strings.
217 verbose: Whether the commands should be shown. Default to the global
218 verbosity if unspecified.
219 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
220 stdin, etc. stdout and stderr will default to subprocess.PIPE and
221 subprocess.STDOUT respectively unless caller specifies any of them.
222
Bill Peckham889b0c62019-02-21 18:53:37 -0800223 Raises:
224 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800225 """
226 proc = Run(args, verbose=verbose, **kwargs)
227 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800228
229 if proc.returncode != 0:
230 raise ExternalError(
231 "Failed to run command '{}' (exit code {})".format(
232 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800233
234
Tao Bao986ee862018-10-04 15:46:16 -0700235def RunAndCheckOutput(args, verbose=None, **kwargs):
236 """Runs the given command and returns the output.
237
238 Args:
239 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700240 verbose: Whether the commands should be shown. Default to the global
241 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700242 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
243 stdin, etc. stdout and stderr will default to subprocess.PIPE and
244 subprocess.STDOUT respectively unless caller specifies any of them.
245
246 Returns:
247 The output string.
248
249 Raises:
250 ExternalError: On non-zero exit from the command.
251 """
Tao Bao986ee862018-10-04 15:46:16 -0700252 proc = Run(args, verbose=verbose, **kwargs)
253 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800254 if output is None:
255 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700256 # Don't log any if caller explicitly says so.
257 if verbose != False:
258 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700259 if proc.returncode != 0:
260 raise ExternalError(
261 "Failed to run command '{}' (exit code {}):\n{}".format(
262 args, proc.returncode, output))
263 return output
264
265
Tao Baoc765cca2018-01-31 17:32:40 -0800266def RoundUpTo4K(value):
267 rounded_up = value + 4095
268 return rounded_up - (rounded_up % 4096)
269
270
Ying Wang7e6d4e42010-12-13 16:25:36 -0800271def CloseInheritedPipes():
272 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
273 before doing other work."""
274 if platform.system() != "Darwin":
275 return
276 for d in range(3, 1025):
277 try:
278 stat = os.fstat(d)
279 if stat is not None:
280 pipebit = stat[0] & 0x1000
281 if pipebit != 0:
282 os.close(d)
283 except OSError:
284 pass
285
286
Tao Bao1c320f82019-10-04 23:25:12 -0700287class BuildInfo(object):
288 """A class that holds the information for a given build.
289
290 This class wraps up the property querying for a given source or target build.
291 It abstracts away the logic of handling OEM-specific properties, and caches
292 the commonly used properties such as fingerprint.
293
294 There are two types of info dicts: a) build-time info dict, which is generated
295 at build time (i.e. included in a target_files zip); b) OEM info dict that is
296 specified at package generation time (via command line argument
297 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
298 having "oem_fingerprint_properties" in build-time info dict), all the queries
299 would be answered based on build-time info dict only. Otherwise if using
300 OEM-specific properties, some of them will be calculated from two info dicts.
301
302 Users can query properties similarly as using a dict() (e.g. info['fstab']),
303 or to query build properties via GetBuildProp() or GetVendorBuildProp().
304
305 Attributes:
306 info_dict: The build-time info dict.
307 is_ab: Whether it's a build that uses A/B OTA.
308 oem_dicts: A list of OEM dicts.
309 oem_props: A list of OEM properties that should be read from OEM dicts; None
310 if the build doesn't use any OEM-specific property.
311 fingerprint: The fingerprint of the build, which would be calculated based
312 on OEM properties if applicable.
313 device: The device name, which could come from OEM dicts if applicable.
314 """
315
316 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
317 "ro.product.manufacturer", "ro.product.model",
318 "ro.product.name"]
319 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
320 "system_ext", "system"]
321
Tao Bao3ed35d32019-10-07 20:48:48 -0700322 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700323 """Initializes a BuildInfo instance with the given dicts.
324
325 Note that it only wraps up the given dicts, without making copies.
326
327 Arguments:
328 info_dict: The build-time info dict.
329 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
330 that it always uses the first dict to calculate the fingerprint or the
331 device name. The rest would be used for asserting OEM properties only
332 (e.g. one package can be installed on one of these devices).
333
334 Raises:
335 ValueError: On invalid inputs.
336 """
337 self.info_dict = info_dict
338 self.oem_dicts = oem_dicts
339
340 self._is_ab = info_dict.get("ab_update") == "true"
341 self._oem_props = info_dict.get("oem_fingerprint_properties")
342
343 if self._oem_props:
344 assert oem_dicts, "OEM source required for this build"
345
346 # These two should be computed only after setting self._oem_props.
347 self._device = self.GetOemProperty("ro.product.device")
348 self._fingerprint = self.CalculateFingerprint()
349
350 # Sanity check the build fingerprint.
351 if (' ' in self._fingerprint or
352 any(ord(ch) > 127 for ch in self._fingerprint)):
353 raise ValueError(
354 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
355 '3.2.2. Build Parameters.'.format(self._fingerprint))
356
357 @property
358 def is_ab(self):
359 return self._is_ab
360
361 @property
362 def device(self):
363 return self._device
364
365 @property
366 def fingerprint(self):
367 return self._fingerprint
368
369 @property
370 def vendor_fingerprint(self):
371 return self._fingerprint_of("vendor")
372
373 @property
374 def product_fingerprint(self):
375 return self._fingerprint_of("product")
376
377 @property
378 def odm_fingerprint(self):
379 return self._fingerprint_of("odm")
380
381 def _fingerprint_of(self, partition):
382 if partition + ".build.prop" not in self.info_dict:
383 return None
384 build_prop = self.info_dict[partition + ".build.prop"]
385 if "ro." + partition + ".build.fingerprint" in build_prop:
386 return build_prop["ro." + partition + ".build.fingerprint"]
387 if "ro." + partition + ".build.thumbprint" in build_prop:
388 return build_prop["ro." + partition + ".build.thumbprint"]
389 return None
390
391 @property
392 def oem_props(self):
393 return self._oem_props
394
395 def __getitem__(self, key):
396 return self.info_dict[key]
397
398 def __setitem__(self, key, value):
399 self.info_dict[key] = value
400
401 def get(self, key, default=None):
402 return self.info_dict.get(key, default)
403
404 def items(self):
405 return self.info_dict.items()
406
407 def GetBuildProp(self, prop):
408 """Returns the inquired build property."""
409 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
410 return self._ResolveRoProductBuildProp(prop)
411
412 try:
413 return self.info_dict.get("build.prop", {})[prop]
414 except KeyError:
415 raise ExternalError("couldn't find %s in build.prop" % (prop,))
416
417 def _ResolveRoProductBuildProp(self, prop):
418 """Resolves the inquired ro.product.* build property"""
419 prop_val = self.info_dict.get("build.prop", {}).get(prop)
420 if prop_val:
421 return prop_val
422
423 source_order_val = self.info_dict.get("build.prop", {}).get(
424 "ro.product.property_source_order")
425 if source_order_val:
426 source_order = source_order_val.split(",")
427 else:
428 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
429
430 # Check that all sources in ro.product.property_source_order are valid
431 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
432 for x in source_order]):
433 raise ExternalError(
434 "Invalid ro.product.property_source_order '{}'".format(source_order))
435
436 for source in source_order:
437 source_prop = prop.replace(
438 "ro.product", "ro.product.{}".format(source), 1)
439 prop_val = self.info_dict.get(
440 "{}.build.prop".format(source), {}).get(source_prop)
441 if prop_val:
442 return prop_val
443
444 raise ExternalError("couldn't resolve {}".format(prop))
445
446 def GetVendorBuildProp(self, prop):
447 """Returns the inquired vendor build property."""
448 try:
449 return self.info_dict.get("vendor.build.prop", {})[prop]
450 except KeyError:
451 raise ExternalError(
452 "couldn't find %s in vendor.build.prop" % (prop,))
453
454 def GetOemProperty(self, key):
455 if self.oem_props is not None and key in self.oem_props:
456 return self.oem_dicts[0][key]
457 return self.GetBuildProp(key)
458
459 def CalculateFingerprint(self):
460 if self.oem_props is None:
461 try:
462 return self.GetBuildProp("ro.build.fingerprint")
463 except ExternalError:
464 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
465 self.GetBuildProp("ro.product.brand"),
466 self.GetBuildProp("ro.product.name"),
467 self.GetBuildProp("ro.product.device"),
468 self.GetBuildProp("ro.build.version.release"),
469 self.GetBuildProp("ro.build.id"),
470 self.GetBuildProp("ro.build.version.incremental"),
471 self.GetBuildProp("ro.build.type"),
472 self.GetBuildProp("ro.build.tags"))
473 return "%s/%s/%s:%s" % (
474 self.GetOemProperty("ro.product.brand"),
475 self.GetOemProperty("ro.product.name"),
476 self.GetOemProperty("ro.product.device"),
477 self.GetBuildProp("ro.build.thumbprint"))
478
479 def WriteMountOemScript(self, script):
480 assert self.oem_props is not None
481 recovery_mount_options = self.info_dict.get("recovery_mount_options")
482 script.Mount("/oem", recovery_mount_options)
483
484 def WriteDeviceAssertions(self, script, oem_no_mount):
485 # Read the property directly if not using OEM properties.
486 if not self.oem_props:
487 script.AssertDevice(self.device)
488 return
489
490 # Otherwise assert OEM properties.
491 if not self.oem_dicts:
492 raise ExternalError(
493 "No OEM file provided to answer expected assertions")
494
495 for prop in self.oem_props.split():
496 values = []
497 for oem_dict in self.oem_dicts:
498 if prop in oem_dict:
499 values.append(oem_dict[prop])
500 if not values:
501 raise ExternalError(
502 "The OEM file is missing the property %s" % (prop,))
503 script.AssertOemProperty(prop, values, oem_no_mount)
504
505
Tao Bao410ad8b2018-08-24 12:08:38 -0700506def LoadInfoDict(input_file, repacking=False):
507 """Loads the key/value pairs from the given input target_files.
508
509 It reads `META/misc_info.txt` file in the target_files input, does sanity
510 checks and returns the parsed key/value pairs for to the given build. It's
511 usually called early when working on input target_files files, e.g. when
512 generating OTAs, or signing builds. Note that the function may be called
513 against an old target_files file (i.e. from past dessert releases). So the
514 property parsing needs to be backward compatible.
515
516 In a `META/misc_info.txt`, a few properties are stored as links to the files
517 in the PRODUCT_OUT directory. It works fine with the build system. However,
518 they are no longer available when (re)generating images from target_files zip.
519 When `repacking` is True, redirect these properties to the actual files in the
520 unzipped directory.
521
522 Args:
523 input_file: The input target_files file, which could be an open
524 zipfile.ZipFile instance, or a str for the dir that contains the files
525 unzipped from a target_files file.
526 repacking: Whether it's trying repack an target_files file after loading the
527 info dict (default: False). If so, it will rewrite a few loaded
528 properties (e.g. selinux_fc, root_dir) to point to the actual files in
529 target_files file. When doing repacking, `input_file` must be a dir.
530
531 Returns:
532 A dict that contains the parsed key/value pairs.
533
534 Raises:
535 AssertionError: On invalid input arguments.
536 ValueError: On malformed input values.
537 """
538 if repacking:
539 assert isinstance(input_file, str), \
540 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700541
Doug Zongkerc9253822014-02-04 12:17:58 -0800542 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700543 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800544 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800545 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700546 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800547 try:
548 with open(path) as f:
549 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700550 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800551 if e.errno == errno.ENOENT:
552 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800553
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700554 try:
Michael Runge6e836112014-04-15 17:40:21 -0700555 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700556 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700557 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700558
Tao Bao410ad8b2018-08-24 12:08:38 -0700559 if "recovery_api_version" not in d:
560 raise ValueError("Failed to find 'recovery_api_version'")
561 if "fstab_version" not in d:
562 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800563
Tao Bao410ad8b2018-08-24 12:08:38 -0700564 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700565 # "selinux_fc" properties should point to the file_contexts files
566 # (file_contexts.bin) under META/.
567 for key in d:
568 if key.endswith("selinux_fc"):
569 fc_basename = os.path.basename(d[key])
570 fc_config = os.path.join(input_file, "META", fc_basename)
571 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700572
Daniel Norman72c626f2019-05-13 15:58:14 -0700573 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700574
Tom Cherryd14b8952018-08-09 14:26:00 -0700575 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700576 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700577 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700578 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700579
Tao Baof54216f2016-03-29 15:12:37 -0700580 # Redirect {system,vendor}_base_fs_file.
581 if "system_base_fs_file" in d:
582 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700583 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700584 if os.path.exists(system_base_fs_file):
585 d["system_base_fs_file"] = system_base_fs_file
586 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700587 logger.warning(
588 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700589 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700590
591 if "vendor_base_fs_file" in d:
592 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700593 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700594 if os.path.exists(vendor_base_fs_file):
595 d["vendor_base_fs_file"] = vendor_base_fs_file
596 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700597 logger.warning(
598 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700599 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700600
Doug Zongker37974732010-09-16 17:44:38 -0700601 def makeint(key):
602 if key in d:
603 d[key] = int(d[key], 0)
604
605 makeint("recovery_api_version")
606 makeint("blocksize")
607 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700608 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700609 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700610 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700611 makeint("recovery_size")
612 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800613 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700614
Tao Bao765668f2019-10-04 22:03:00 -0700615 # Load recovery fstab if applicable.
616 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800617
Tianjie Xu861f4132018-09-12 11:49:33 -0700618 # Tries to load the build props for all partitions with care_map, including
619 # system and vendor.
620 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800621 partition_prop = "{}.build.prop".format(partition)
622 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700623 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800624 # Some partition might use /<partition>/etc/build.prop as the new path.
625 # TODO: try new path first when majority of them switch to the new path.
626 if not d[partition_prop]:
627 d[partition_prop] = LoadBuildProp(
628 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700629 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800630
Tao Bao3ed35d32019-10-07 20:48:48 -0700631 # Set up the salt (based on fingerprint) that will be used when adding AVB
632 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800633 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700634 build_info = BuildInfo(d)
635 d["avb_salt"] = sha256(build_info.fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800636
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700637 return d
638
Tao Baod1de6f32017-03-01 16:38:48 -0800639
Tao Baobcd1d162017-08-26 13:10:26 -0700640def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700641 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700642 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700643 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700644 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700645 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700646 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700647
Tao Baod1de6f32017-03-01 16:38:48 -0800648
Daniel Norman4cc9df62019-07-18 10:11:07 -0700649def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900650 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700651 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900652
Daniel Norman4cc9df62019-07-18 10:11:07 -0700653
654def LoadDictionaryFromFile(file_path):
655 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900656 return LoadDictionaryFromLines(lines)
657
658
Michael Runge6e836112014-04-15 17:40:21 -0700659def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700660 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700661 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700662 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700663 if not line or line.startswith("#"):
664 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700665 if "=" in line:
666 name, value = line.split("=", 1)
667 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700668 return d
669
Tao Baod1de6f32017-03-01 16:38:48 -0800670
Tianjie Xucfa86222016-03-07 16:31:19 -0800671def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
672 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700673 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800674 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700675 self.mount_point = mount_point
676 self.fs_type = fs_type
677 self.device = device
678 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700679 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700680
681 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800682 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700683 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700684 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700685 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700686
Tao Baod1de6f32017-03-01 16:38:48 -0800687 assert fstab_version == 2
688
689 d = {}
690 for line in data.split("\n"):
691 line = line.strip()
692 if not line or line.startswith("#"):
693 continue
694
695 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
696 pieces = line.split()
697 if len(pieces) != 5:
698 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
699
700 # Ignore entries that are managed by vold.
701 options = pieces[4]
702 if "voldmanaged=" in options:
703 continue
704
705 # It's a good line, parse it.
706 length = 0
707 options = options.split(",")
708 for i in options:
709 if i.startswith("length="):
710 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800711 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800712 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700713 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800714
Tao Baod1de6f32017-03-01 16:38:48 -0800715 mount_flags = pieces[3]
716 # Honor the SELinux context if present.
717 context = None
718 for i in mount_flags.split(","):
719 if i.startswith("context="):
720 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800721
Tao Baod1de6f32017-03-01 16:38:48 -0800722 mount_point = pieces[1]
723 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
724 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800725
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700726 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700727 # system. Other areas assume system is always at "/system" so point /system
728 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700729 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800730 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700731 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700732 return d
733
734
Tao Bao765668f2019-10-04 22:03:00 -0700735def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
736 """Finds the path to recovery fstab and loads its contents."""
737 # recovery fstab is only meaningful when installing an update via recovery
738 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
739 if info_dict.get('ab_update') == 'true':
740 return None
741
742 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
743 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
744 # cases, since it may load the info_dict from an old build (e.g. when
745 # generating incremental OTAs from that build).
746 system_root_image = info_dict.get('system_root_image') == 'true'
747 if info_dict.get('no_recovery') != 'true':
748 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
749 if isinstance(input_file, zipfile.ZipFile):
750 if recovery_fstab_path not in input_file.namelist():
751 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
752 else:
753 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
754 if not os.path.exists(path):
755 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
756 return LoadRecoveryFSTab(
757 read_helper, info_dict['fstab_version'], recovery_fstab_path,
758 system_root_image)
759
760 if info_dict.get('recovery_as_boot') == 'true':
761 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
762 if isinstance(input_file, zipfile.ZipFile):
763 if recovery_fstab_path not in input_file.namelist():
764 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
765 else:
766 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
767 if not os.path.exists(path):
768 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
769 return LoadRecoveryFSTab(
770 read_helper, info_dict['fstab_version'], recovery_fstab_path,
771 system_root_image)
772
773 return None
774
775
Doug Zongker37974732010-09-16 17:44:38 -0700776def DumpInfoDict(d):
777 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700778 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700779
Dan Albert8b72aef2015-03-23 19:13:21 -0700780
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700781def MergeDynamicPartitionInfoDicts(framework_dict,
782 vendor_dict,
783 include_dynamic_partition_list=True,
784 size_prefix="",
785 size_suffix="",
786 list_prefix="",
787 list_suffix=""):
788 """Merges dynamic partition info variables.
789
790 Args:
791 framework_dict: The dictionary of dynamic partition info variables from the
792 partial framework target files.
793 vendor_dict: The dictionary of dynamic partition info variables from the
794 partial vendor target files.
795 include_dynamic_partition_list: If true, merges the dynamic_partition_list
796 variable. Not all use cases need this variable merged.
797 size_prefix: The prefix in partition group size variables that precedes the
798 name of the partition group. For example, partition group 'group_a' with
799 corresponding size variable 'super_group_a_group_size' would have the
800 size_prefix 'super_'.
801 size_suffix: Similar to size_prefix but for the variable's suffix. For
802 example, 'super_group_a_group_size' would have size_suffix '_group_size'.
803 list_prefix: Similar to size_prefix but for the partition group's
804 partition_list variable.
805 list_suffix: Similar to size_suffix but for the partition group's
806 partition_list variable.
807
808 Returns:
809 The merged dynamic partition info dictionary.
810 """
811 merged_dict = {}
812 # Partition groups and group sizes are defined by the vendor dict because
813 # these values may vary for each board that uses a shared system image.
814 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
815 if include_dynamic_partition_list:
816 framework_dynamic_partition_list = framework_dict.get(
817 "dynamic_partition_list", "")
818 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list",
819 "")
820 merged_dict["dynamic_partition_list"] = (
821 "%s %s" % (framework_dynamic_partition_list,
822 vendor_dynamic_partition_list)).strip()
823 for partition_group in merged_dict["super_partition_groups"].split(" "):
824 # Set the partition group's size using the value from the vendor dict.
825 key = "%s%s%s" % (size_prefix, partition_group, size_suffix)
826 if key not in vendor_dict:
827 raise ValueError("Vendor dict does not contain required key %s." % key)
828 merged_dict[key] = vendor_dict[key]
829
830 # Set the partition group's partition list using a concatenation of the
831 # framework and vendor partition lists.
832 key = "%s%s%s" % (list_prefix, partition_group, list_suffix)
833 merged_dict[key] = (
834 "%s %s" %
835 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
836 return merged_dict
837
838
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800839def AppendAVBSigningArgs(cmd, partition):
840 """Append signing arguments for avbtool."""
841 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
842 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700843 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
844 new_key_path = os.path.join(OPTIONS.search_path, key_path)
845 if os.path.exists(new_key_path):
846 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800847 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
848 if key_path and algorithm:
849 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700850 avb_salt = OPTIONS.info_dict.get("avb_salt")
851 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700852 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700853 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800854
855
Tao Bao765668f2019-10-04 22:03:00 -0700856def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700857 """Returns the VBMeta arguments for partition.
858
859 It sets up the VBMeta argument by including the partition descriptor from the
860 given 'image', or by configuring the partition as a chained partition.
861
862 Args:
863 partition: The name of the partition (e.g. "system").
864 image: The path to the partition image.
865 info_dict: A dict returned by common.LoadInfoDict(). Will use
866 OPTIONS.info_dict if None has been given.
867
868 Returns:
869 A list of VBMeta arguments.
870 """
871 if info_dict is None:
872 info_dict = OPTIONS.info_dict
873
874 # Check if chain partition is used.
875 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +0800876 if not key_path:
877 return ["--include_descriptors_from_image", image]
878
879 # For a non-A/B device, we don't chain /recovery nor include its descriptor
880 # into vbmeta.img. The recovery image will be configured on an independent
881 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
882 # See details at
883 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
884 if OPTIONS.info_dict.get("ab_update") != "true" and partition == "recovery":
885 return []
886
887 # Otherwise chain the partition into vbmeta.
888 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
889 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -0700890
891
Tao Bao02a08592018-07-22 12:40:45 -0700892def GetAvbChainedPartitionArg(partition, info_dict, key=None):
893 """Constructs and returns the arg to build or verify a chained partition.
894
895 Args:
896 partition: The partition name.
897 info_dict: The info dict to look up the key info and rollback index
898 location.
899 key: The key to be used for building or verifying the partition. Defaults to
900 the key listed in info_dict.
901
902 Returns:
903 A string of form "partition:rollback_index_location:key" that can be used to
904 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700905 """
906 if key is None:
907 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700908 if key and not os.path.exists(key) and OPTIONS.search_path:
909 new_key_path = os.path.join(OPTIONS.search_path, key)
910 if os.path.exists(new_key_path):
911 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700912 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700913 rollback_index_location = info_dict[
914 "avb_" + partition + "_rollback_index_location"]
915 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
916
917
Daniel Norman276f0622019-07-26 14:13:51 -0700918def BuildVBMeta(image_path, partitions, name, needed_partitions):
919 """Creates a VBMeta image.
920
921 It generates the requested VBMeta image. The requested image could be for
922 top-level or chained VBMeta image, which is determined based on the name.
923
924 Args:
925 image_path: The output path for the new VBMeta image.
926 partitions: A dict that's keyed by partition names with image paths as
927 values. Only valid partition names are accepted, as listed in
928 common.AVB_PARTITIONS.
929 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
930 needed_partitions: Partitions whose descriptors should be included into the
931 generated VBMeta image.
932
933 Raises:
934 AssertionError: On invalid input args.
935 """
936 avbtool = OPTIONS.info_dict["avb_avbtool"]
937 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
938 AppendAVBSigningArgs(cmd, name)
939
940 for partition, path in partitions.items():
941 if partition not in needed_partitions:
942 continue
943 assert (partition in AVB_PARTITIONS or
944 partition in AVB_VBMETA_PARTITIONS), \
945 'Unknown partition: {}'.format(partition)
946 assert os.path.exists(path), \
947 'Failed to find {} for {}'.format(path, partition)
948 cmd.extend(GetAvbPartitionArg(partition, path))
949
950 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
951 if args and args.strip():
952 split_args = shlex.split(args)
953 for index, arg in enumerate(split_args[:-1]):
954 # Sanity check that the image file exists. Some images might be defined
955 # as a path relative to source tree, which may not be available at the
956 # same location when running this script (we have the input target_files
957 # zip only). For such cases, we additionally scan other locations (e.g.
958 # IMAGES/, RADIO/, etc) before bailing out.
959 if arg == '--include_descriptors_from_image':
960 image_path = split_args[index + 1]
961 if os.path.exists(image_path):
962 continue
963 found = False
964 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
965 alt_path = os.path.join(
966 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
967 if os.path.exists(alt_path):
968 split_args[index + 1] = alt_path
969 found = True
970 break
971 assert found, 'Failed to find {}'.format(image_path)
972 cmd.extend(split_args)
973
974 RunAndCheckOutput(cmd)
975
976
Steve Mucklee1b10862019-07-10 10:49:37 -0700977def _MakeRamdisk(sourcedir, fs_config_file=None):
978 ramdisk_img = tempfile.NamedTemporaryFile()
979
980 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
981 cmd = ["mkbootfs", "-f", fs_config_file,
982 os.path.join(sourcedir, "RAMDISK")]
983 else:
984 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
985 p1 = Run(cmd, stdout=subprocess.PIPE)
986 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
987
988 p2.wait()
989 p1.wait()
990 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
991 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
992
993 return ramdisk_img
994
995
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700996def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800997 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700998 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700999
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001000 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001001 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1002 we are building a two-step special image (i.e. building a recovery image to
1003 be loaded into /boot in two-step OTAs).
1004
1005 Return the image data, or None if sourcedir does not appear to contains files
1006 for building the requested image.
1007 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001008
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001009 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
1010 return None
1011
1012 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001013 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001014
Doug Zongkerd5131602012-08-02 14:46:42 -07001015 if info_dict is None:
1016 info_dict = OPTIONS.info_dict
1017
Doug Zongkereef39442009-04-02 12:14:19 -07001018 img = tempfile.NamedTemporaryFile()
1019
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001020 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001021 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001022
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001023 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1024 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1025
1026 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -07001027
Benoit Fradina45a8682014-07-14 21:00:43 +02001028 fn = os.path.join(sourcedir, "second")
1029 if os.access(fn, os.F_OK):
1030 cmd.append("--second")
1031 cmd.append(fn)
1032
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001033 fn = os.path.join(sourcedir, "dtb")
1034 if os.access(fn, os.F_OK):
1035 cmd.append("--dtb")
1036 cmd.append(fn)
1037
Doug Zongker171f1cd2009-06-15 22:36:37 -07001038 fn = os.path.join(sourcedir, "cmdline")
1039 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001040 cmd.append("--cmdline")
1041 cmd.append(open(fn).read().rstrip("\n"))
1042
1043 fn = os.path.join(sourcedir, "base")
1044 if os.access(fn, os.F_OK):
1045 cmd.append("--base")
1046 cmd.append(open(fn).read().rstrip("\n"))
1047
Ying Wang4de6b5b2010-08-25 14:29:34 -07001048 fn = os.path.join(sourcedir, "pagesize")
1049 if os.access(fn, os.F_OK):
1050 cmd.append("--pagesize")
1051 cmd.append(open(fn).read().rstrip("\n"))
1052
Tao Bao76def242017-11-21 09:25:31 -08001053 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001054 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001055 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001056
Tao Bao76def242017-11-21 09:25:31 -08001057 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001058 if args and args.strip():
1059 cmd.extend(shlex.split(args))
1060
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001061 if has_ramdisk:
1062 cmd.extend(["--ramdisk", ramdisk_img.name])
1063
Tao Baod95e9fd2015-03-29 23:07:41 -07001064 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001065 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001066 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001067 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001068 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001069 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001070
Tao Baobf70c3182017-07-11 17:27:55 -07001071 # "boot" or "recovery", without extension.
1072 partition_name = os.path.basename(sourcedir).lower()
1073
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001074 if partition_name == "recovery":
1075 if info_dict.get("include_recovery_dtbo") == "true":
1076 fn = os.path.join(sourcedir, "recovery_dtbo")
1077 cmd.extend(["--recovery_dtbo", fn])
1078 if info_dict.get("include_recovery_acpio") == "true":
1079 fn = os.path.join(sourcedir, "recovery_acpio")
1080 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001081
Tao Bao986ee862018-10-04 15:46:16 -07001082 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001083
Tao Bao76def242017-11-21 09:25:31 -08001084 if (info_dict.get("boot_signer") == "true" and
1085 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001086 # Hard-code the path as "/boot" for two-step special recovery image (which
1087 # will be loaded into /boot during the two-step OTA).
1088 if two_step_image:
1089 path = "/boot"
1090 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001091 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001092 cmd = [OPTIONS.boot_signer_path]
1093 cmd.extend(OPTIONS.boot_signer_args)
1094 cmd.extend([path, img.name,
1095 info_dict["verity_key"] + ".pk8",
1096 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001097 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001098
Tao Baod95e9fd2015-03-29 23:07:41 -07001099 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001100 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001101 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001102 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001103 # We have switched from the prebuilt futility binary to using the tool
1104 # (futility-host) built from the source. Override the setting in the old
1105 # TF.zip.
1106 futility = info_dict["futility"]
1107 if futility.startswith("prebuilts/"):
1108 futility = "futility-host"
1109 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001110 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001111 info_dict["vboot_key"] + ".vbprivk",
1112 info_dict["vboot_subkey"] + ".vbprivk",
1113 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001114 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001115 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001116
Tao Baof3282b42015-04-01 11:21:55 -07001117 # Clean up the temp files.
1118 img_unsigned.close()
1119 img_keyblock.close()
1120
David Zeuthen8fecb282017-12-01 16:24:01 -05001121 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001122 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001123 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001124 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001125 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001126 "--partition_size", str(part_size), "--partition_name",
1127 partition_name]
1128 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001129 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001130 if args and args.strip():
1131 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001132 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001133
1134 img.seek(os.SEEK_SET, 0)
1135 data = img.read()
1136
1137 if has_ramdisk:
1138 ramdisk_img.close()
1139 img.close()
1140
1141 return data
1142
1143
Doug Zongkerd5131602012-08-02 14:46:42 -07001144def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001145 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001146 """Return a File object with the desired bootable image.
1147
1148 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1149 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1150 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001151
Doug Zongker55d93282011-01-25 17:03:34 -08001152 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1153 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001154 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001155 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001156
1157 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1158 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001159 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001160 return File.FromLocalFile(name, prebuilt_path)
1161
Tao Bao32fcdab2018-10-12 10:30:39 -07001162 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001163
1164 if info_dict is None:
1165 info_dict = OPTIONS.info_dict
1166
1167 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001168 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1169 # for recovery.
1170 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1171 prebuilt_name != "boot.img" or
1172 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001173
Doug Zongker6f1d0312014-08-22 08:07:12 -07001174 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001175 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
1176 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001177 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001178 if data:
1179 return File(name, data)
1180 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001181
Doug Zongkereef39442009-04-02 12:14:19 -07001182
Steve Mucklee1b10862019-07-10 10:49:37 -07001183def _BuildVendorBootImage(sourcedir, info_dict=None):
1184 """Build a vendor boot image from the specified sourcedir.
1185
1186 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1187 turn them into a vendor boot image.
1188
1189 Return the image data, or None if sourcedir does not appear to contains files
1190 for building the requested image.
1191 """
1192
1193 if info_dict is None:
1194 info_dict = OPTIONS.info_dict
1195
1196 img = tempfile.NamedTemporaryFile()
1197
1198 ramdisk_img = _MakeRamdisk(sourcedir)
1199
1200 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1201 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1202
1203 cmd = [mkbootimg]
1204
1205 fn = os.path.join(sourcedir, "dtb")
1206 if os.access(fn, os.F_OK):
1207 cmd.append("--dtb")
1208 cmd.append(fn)
1209
1210 fn = os.path.join(sourcedir, "vendor_cmdline")
1211 if os.access(fn, os.F_OK):
1212 cmd.append("--vendor_cmdline")
1213 cmd.append(open(fn).read().rstrip("\n"))
1214
1215 fn = os.path.join(sourcedir, "base")
1216 if os.access(fn, os.F_OK):
1217 cmd.append("--base")
1218 cmd.append(open(fn).read().rstrip("\n"))
1219
1220 fn = os.path.join(sourcedir, "pagesize")
1221 if os.access(fn, os.F_OK):
1222 cmd.append("--pagesize")
1223 cmd.append(open(fn).read().rstrip("\n"))
1224
1225 args = info_dict.get("mkbootimg_args")
1226 if args and args.strip():
1227 cmd.extend(shlex.split(args))
1228
1229 args = info_dict.get("mkbootimg_version_args")
1230 if args and args.strip():
1231 cmd.extend(shlex.split(args))
1232
1233 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1234 cmd.extend(["--vendor_boot", img.name])
1235
1236 RunAndCheckOutput(cmd)
1237
1238 # AVB: if enabled, calculate and add hash.
1239 if info_dict.get("avb_enable") == "true":
1240 avbtool = info_dict["avb_avbtool"]
1241 part_size = info_dict["vendor_boot_size"]
1242 cmd = [avbtool, "add_hash_footer", "--image", img.name,
1243 "--partition_size", str(part_size), "--partition_name vendor_boot"]
1244 AppendAVBSigningArgs(cmd, "vendor_boot")
1245 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1246 if args and args.strip():
1247 cmd.extend(shlex.split(args))
1248 RunAndCheckOutput(cmd)
1249
1250 img.seek(os.SEEK_SET, 0)
1251 data = img.read()
1252
1253 ramdisk_img.close()
1254 img.close()
1255
1256 return data
1257
1258
1259def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1260 info_dict=None):
1261 """Return a File object with the desired vendor boot image.
1262
1263 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1264 the source files in 'unpack_dir'/'tree_subdir'."""
1265
1266 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1267 if os.path.exists(prebuilt_path):
1268 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1269 return File.FromLocalFile(name, prebuilt_path)
1270
1271 logger.info("building image from target_files %s...", tree_subdir)
1272
1273 if info_dict is None:
1274 info_dict = OPTIONS.info_dict
1275
1276 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1277 if data:
1278 return File(name, data)
1279 return None
1280
1281
Narayan Kamatha07bf042017-08-14 14:49:21 +01001282def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001283 """Gunzips the given gzip compressed file to a given output file."""
1284 with gzip.open(in_filename, "rb") as in_file, \
1285 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001286 shutil.copyfileobj(in_file, out_file)
1287
1288
Tao Bao0ff15de2019-03-20 11:26:06 -07001289def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001290 """Unzips the archive to the given directory.
1291
1292 Args:
1293 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001294 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001295 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1296 archvie. Non-matching patterns will be filtered out. If there's no match
1297 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001298 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001299 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001300 if patterns is not None:
1301 # Filter out non-matching patterns. unzip will complain otherwise.
1302 with zipfile.ZipFile(filename) as input_zip:
1303 names = input_zip.namelist()
1304 filtered = [
1305 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1306
1307 # There isn't any matching files. Don't unzip anything.
1308 if not filtered:
1309 return
1310 cmd.extend(filtered)
1311
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001312 RunAndCheckOutput(cmd)
1313
1314
Doug Zongker75f17362009-12-08 13:46:44 -08001315def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001316 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001317
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001318 Args:
1319 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1320 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1321
1322 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1323 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001324
Tao Bao1c830bf2017-12-25 10:43:47 -08001325 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001326 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001327 """
Doug Zongkereef39442009-04-02 12:14:19 -07001328
Tao Bao1c830bf2017-12-25 10:43:47 -08001329 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001330 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1331 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001332 UnzipToDir(m.group(1), tmp, pattern)
1333 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001334 filename = m.group(1)
1335 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001336 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001337
Tao Baodba59ee2018-01-09 13:21:02 -08001338 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001339
1340
Yifan Hong8a66a712019-04-04 15:37:57 -07001341def GetUserImage(which, tmpdir, input_zip,
1342 info_dict=None,
1343 allow_shared_blocks=None,
1344 hashtree_info_generator=None,
1345 reset_file_map=False):
1346 """Returns an Image object suitable for passing to BlockImageDiff.
1347
1348 This function loads the specified image from the given path. If the specified
1349 image is sparse, it also performs additional processing for OTA purpose. For
1350 example, it always adds block 0 to clobbered blocks list. It also detects
1351 files that cannot be reconstructed from the block list, for whom we should
1352 avoid applying imgdiff.
1353
1354 Args:
1355 which: The partition name.
1356 tmpdir: The directory that contains the prebuilt image and block map file.
1357 input_zip: The target-files ZIP archive.
1358 info_dict: The dict to be looked up for relevant info.
1359 allow_shared_blocks: If image is sparse, whether having shared blocks is
1360 allowed. If none, it is looked up from info_dict.
1361 hashtree_info_generator: If present and image is sparse, generates the
1362 hashtree_info for this sparse image.
1363 reset_file_map: If true and image is sparse, reset file map before returning
1364 the image.
1365 Returns:
1366 A Image object. If it is a sparse image and reset_file_map is False, the
1367 image will have file_map info loaded.
1368 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001369 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001370 info_dict = LoadInfoDict(input_zip)
1371
1372 is_sparse = info_dict.get("extfs_sparse_flag")
1373
1374 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1375 # shared blocks (i.e. some blocks will show up in multiple files' block
1376 # list). We can only allocate such shared blocks to the first "owner", and
1377 # disable imgdiff for all later occurrences.
1378 if allow_shared_blocks is None:
1379 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1380
1381 if is_sparse:
1382 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1383 hashtree_info_generator)
1384 if reset_file_map:
1385 img.ResetFileMap()
1386 return img
1387 else:
1388 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1389
1390
1391def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1392 """Returns a Image object suitable for passing to BlockImageDiff.
1393
1394 This function loads the specified non-sparse image from the given path.
1395
1396 Args:
1397 which: The partition name.
1398 tmpdir: The directory that contains the prebuilt image and block map file.
1399 Returns:
1400 A Image object.
1401 """
1402 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1403 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1404
1405 # The image and map files must have been created prior to calling
1406 # ota_from_target_files.py (since LMP).
1407 assert os.path.exists(path) and os.path.exists(mappath)
1408
Tianjie Xu41976c72019-07-03 13:57:01 -07001409 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1410
Yifan Hong8a66a712019-04-04 15:37:57 -07001411
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001412def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1413 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001414 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1415
1416 This function loads the specified sparse image from the given path, and
1417 performs additional processing for OTA purpose. For example, it always adds
1418 block 0 to clobbered blocks list. It also detects files that cannot be
1419 reconstructed from the block list, for whom we should avoid applying imgdiff.
1420
1421 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001422 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001423 tmpdir: The directory that contains the prebuilt image and block map file.
1424 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001425 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001426 hashtree_info_generator: If present, generates the hashtree_info for this
1427 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001428 Returns:
1429 A SparseImage object, with file_map info loaded.
1430 """
Tao Baoc765cca2018-01-31 17:32:40 -08001431 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1432 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1433
1434 # The image and map files must have been created prior to calling
1435 # ota_from_target_files.py (since LMP).
1436 assert os.path.exists(path) and os.path.exists(mappath)
1437
1438 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1439 # it to clobbered_blocks so that it will be written to the target
1440 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1441 clobbered_blocks = "0"
1442
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001443 image = sparse_img.SparseImage(
1444 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1445 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001446
1447 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1448 # if they contain all zeros. We can't reconstruct such a file from its block
1449 # list. Tag such entries accordingly. (Bug: 65213616)
1450 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001451 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001452 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001453 continue
1454
Tom Cherryd14b8952018-08-09 14:26:00 -07001455 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1456 # filename listed in system.map may contain an additional leading slash
1457 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1458 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001459 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001460
Tom Cherryd14b8952018-08-09 14:26:00 -07001461 # Special handling another case, where files not under /system
1462 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001463 if which == 'system' and not arcname.startswith('SYSTEM'):
1464 arcname = 'ROOT/' + arcname
1465
1466 assert arcname in input_zip.namelist(), \
1467 "Failed to find the ZIP entry for {}".format(entry)
1468
Tao Baoc765cca2018-01-31 17:32:40 -08001469 info = input_zip.getinfo(arcname)
1470 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001471
1472 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001473 # image, check the original block list to determine its completeness. Note
1474 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001475 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001476 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001477
Tao Baoc765cca2018-01-31 17:32:40 -08001478 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1479 ranges.extra['incomplete'] = True
1480
1481 return image
1482
1483
Doug Zongkereef39442009-04-02 12:14:19 -07001484def GetKeyPasswords(keylist):
1485 """Given a list of keys, prompt the user to enter passwords for
1486 those which require them. Return a {key: password} dict. password
1487 will be None if the key has no password."""
1488
Doug Zongker8ce7c252009-05-22 13:34:54 -07001489 no_passwords = []
1490 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001491 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001492 devnull = open("/dev/null", "w+b")
1493 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001494 # We don't need a password for things that aren't really keys.
1495 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001496 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001497 continue
1498
T.R. Fullhart37e10522013-03-18 10:31:26 -07001499 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001500 "-inform", "DER", "-nocrypt"],
1501 stdin=devnull.fileno(),
1502 stdout=devnull.fileno(),
1503 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001504 p.communicate()
1505 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001506 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001507 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001508 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001509 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1510 "-inform", "DER", "-passin", "pass:"],
1511 stdin=devnull.fileno(),
1512 stdout=devnull.fileno(),
1513 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001514 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001515 if p.returncode == 0:
1516 # Encrypted key with empty string as password.
1517 key_passwords[k] = ''
1518 elif stderr.startswith('Error decrypting key'):
1519 # Definitely encrypted key.
1520 # It would have said "Error reading key" if it didn't parse correctly.
1521 need_passwords.append(k)
1522 else:
1523 # Potentially, a type of key that openssl doesn't understand.
1524 # We'll let the routines in signapk.jar handle it.
1525 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001526 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001527
T.R. Fullhart37e10522013-03-18 10:31:26 -07001528 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001529 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001530 return key_passwords
1531
1532
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001533def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001534 """Gets the minSdkVersion declared in the APK.
1535
changho.shin0f125362019-07-08 10:59:00 +09001536 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001537 This can be both a decimal number (API Level) or a codename.
1538
1539 Args:
1540 apk_name: The APK filename.
1541
1542 Returns:
1543 The parsed SDK version string.
1544
1545 Raises:
1546 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001547 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001548 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001549 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001550 stderr=subprocess.PIPE)
1551 stdoutdata, stderrdata = proc.communicate()
1552 if proc.returncode != 0:
1553 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001554 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001555 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001556
Tao Baof47bf0f2018-03-21 23:28:51 -07001557 for line in stdoutdata.split("\n"):
1558 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001559 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1560 if m:
1561 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001562 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001563
1564
1565def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001566 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001567
Tao Baof47bf0f2018-03-21 23:28:51 -07001568 If minSdkVersion is set to a codename, it is translated to a number using the
1569 provided map.
1570
1571 Args:
1572 apk_name: The APK filename.
1573
1574 Returns:
1575 The parsed SDK version number.
1576
1577 Raises:
1578 ExternalError: On failing to get the min SDK version number.
1579 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001580 version = GetMinSdkVersion(apk_name)
1581 try:
1582 return int(version)
1583 except ValueError:
1584 # Not a decimal number. Codename?
1585 if version in codename_to_api_level_map:
1586 return codename_to_api_level_map[version]
1587 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001588 raise ExternalError(
1589 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1590 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001591
1592
1593def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001594 codename_to_api_level_map=None, whole_file=False,
1595 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001596 """Sign the input_name zip/jar/apk, producing output_name. Use the
1597 given key and password (the latter may be None if the key does not
1598 have a password.
1599
Doug Zongker951495f2009-08-14 12:44:19 -07001600 If whole_file is true, use the "-w" option to SignApk to embed a
1601 signature that covers the whole file in the archive comment of the
1602 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001603
1604 min_api_level is the API Level (int) of the oldest platform this file may end
1605 up on. If not specified for an APK, the API Level is obtained by interpreting
1606 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1607
1608 codename_to_api_level_map is needed to translate the codename which may be
1609 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001610
1611 Caller may optionally specify extra args to be passed to SignApk, which
1612 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001613 """
Tao Bao76def242017-11-21 09:25:31 -08001614 if codename_to_api_level_map is None:
1615 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001616 if extra_signapk_args is None:
1617 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001618
Alex Klyubin9667b182015-12-10 13:38:50 -08001619 java_library_path = os.path.join(
1620 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1621
Tao Baoe95540e2016-11-08 12:08:53 -08001622 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1623 ["-Djava.library.path=" + java_library_path,
1624 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001625 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001626 if whole_file:
1627 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001628
1629 min_sdk_version = min_api_level
1630 if min_sdk_version is None:
1631 if not whole_file:
1632 min_sdk_version = GetMinSdkVersionInt(
1633 input_name, codename_to_api_level_map)
1634 if min_sdk_version is not None:
1635 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1636
T.R. Fullhart37e10522013-03-18 10:31:26 -07001637 cmd.extend([key + OPTIONS.public_key_suffix,
1638 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001639 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001640
Tao Bao73dd4f42018-10-04 16:25:33 -07001641 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001642 if password is not None:
1643 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001644 stdoutdata, _ = proc.communicate(password)
1645 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001646 raise ExternalError(
1647 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001648 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001649
Doug Zongkereef39442009-04-02 12:14:19 -07001650
Doug Zongker37974732010-09-16 17:44:38 -07001651def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001652 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001653
Tao Bao9dd909e2017-11-14 11:27:32 -08001654 For non-AVB images, raise exception if the data is too big. Print a warning
1655 if the data is nearing the maximum size.
1656
1657 For AVB images, the actual image size should be identical to the limit.
1658
1659 Args:
1660 data: A string that contains all the data for the partition.
1661 target: The partition name. The ".img" suffix is optional.
1662 info_dict: The dict to be looked up for relevant info.
1663 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001664 if target.endswith(".img"):
1665 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001666 mount_point = "/" + target
1667
Ying Wangf8824af2014-06-03 14:07:27 -07001668 fs_type = None
1669 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001670 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001671 if mount_point == "/userdata":
1672 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001673 p = info_dict["fstab"][mount_point]
1674 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001675 device = p.device
1676 if "/" in device:
1677 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001678 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001679 if not fs_type or not limit:
1680 return
Doug Zongkereef39442009-04-02 12:14:19 -07001681
Andrew Boie0f9aec82012-02-14 09:32:52 -08001682 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001683 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1684 # path.
1685 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1686 if size != limit:
1687 raise ExternalError(
1688 "Mismatching image size for %s: expected %d actual %d" % (
1689 target, limit, size))
1690 else:
1691 pct = float(size) * 100.0 / limit
1692 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1693 if pct >= 99.0:
1694 raise ExternalError(msg)
1695 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001696 logger.warning("\n WARNING: %s\n", msg)
1697 else:
1698 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001699
1700
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001701def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001702 """Parses the APK certs info from a given target-files zip.
1703
1704 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1705 tuple with the following elements: (1) a dictionary that maps packages to
1706 certs (based on the "certificate" and "private_key" attributes in the file;
1707 (2) a string representing the extension of compressed APKs in the target files
1708 (e.g ".gz", ".bro").
1709
1710 Args:
1711 tf_zip: The input target_files ZipFile (already open).
1712
1713 Returns:
1714 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1715 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1716 no compressed APKs.
1717 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001718 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001719 compressed_extension = None
1720
Tao Bao0f990332017-09-08 19:02:54 -07001721 # META/apkcerts.txt contains the info for _all_ the packages known at build
1722 # time. Filter out the ones that are not installed.
1723 installed_files = set()
1724 for name in tf_zip.namelist():
1725 basename = os.path.basename(name)
1726 if basename:
1727 installed_files.add(basename)
1728
Tao Baoda30cfa2017-12-01 16:19:46 -08001729 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001730 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001731 if not line:
1732 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001733 m = re.match(
1734 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1735 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1736 line)
1737 if not m:
1738 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001739
Tao Bao818ddf52018-01-05 11:17:34 -08001740 matches = m.groupdict()
1741 cert = matches["CERT"]
1742 privkey = matches["PRIVKEY"]
1743 name = matches["NAME"]
1744 this_compressed_extension = matches["COMPRESSED"]
1745
1746 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1747 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1748 if cert in SPECIAL_CERT_STRINGS and not privkey:
1749 certmap[name] = cert
1750 elif (cert.endswith(OPTIONS.public_key_suffix) and
1751 privkey.endswith(OPTIONS.private_key_suffix) and
1752 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1753 certmap[name] = cert[:-public_key_suffix_len]
1754 else:
1755 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1756
1757 if not this_compressed_extension:
1758 continue
1759
1760 # Only count the installed files.
1761 filename = name + '.' + this_compressed_extension
1762 if filename not in installed_files:
1763 continue
1764
1765 # Make sure that all the values in the compression map have the same
1766 # extension. We don't support multiple compression methods in the same
1767 # system image.
1768 if compressed_extension:
1769 if this_compressed_extension != compressed_extension:
1770 raise ValueError(
1771 "Multiple compressed extensions: {} vs {}".format(
1772 compressed_extension, this_compressed_extension))
1773 else:
1774 compressed_extension = this_compressed_extension
1775
1776 return (certmap,
1777 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001778
1779
Doug Zongkereef39442009-04-02 12:14:19 -07001780COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001781Global options
1782
1783 -p (--path) <dir>
1784 Prepend <dir>/bin to the list of places to search for binaries run by this
1785 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001786
Doug Zongker05d3dea2009-06-22 11:32:31 -07001787 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001788 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001789
Tao Bao30df8b42018-04-23 15:32:53 -07001790 -x (--extra) <key=value>
1791 Add a key/value pair to the 'extras' dict, which device-specific extension
1792 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001793
Doug Zongkereef39442009-04-02 12:14:19 -07001794 -v (--verbose)
1795 Show command lines being executed.
1796
1797 -h (--help)
1798 Display this usage message and exit.
1799"""
1800
1801def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001802 print(docstring.rstrip("\n"))
1803 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001804
1805
1806def ParseOptions(argv,
1807 docstring,
1808 extra_opts="", extra_long_opts=(),
1809 extra_option_handler=None):
1810 """Parse the options in argv and return any arguments that aren't
1811 flags. docstring is the calling module's docstring, to be displayed
1812 for errors and -h. extra_opts and extra_long_opts are for flags
1813 defined by the caller, which are processed by passing them to
1814 extra_option_handler."""
1815
1816 try:
1817 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001818 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001819 ["help", "verbose", "path=", "signapk_path=",
1820 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001821 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001822 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1823 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001824 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001825 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001826 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001827 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001828 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001829 sys.exit(2)
1830
Doug Zongkereef39442009-04-02 12:14:19 -07001831 for o, a in opts:
1832 if o in ("-h", "--help"):
1833 Usage(docstring)
1834 sys.exit()
1835 elif o in ("-v", "--verbose"):
1836 OPTIONS.verbose = True
1837 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001838 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001839 elif o in ("--signapk_path",):
1840 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001841 elif o in ("--signapk_shared_library_path",):
1842 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001843 elif o in ("--extra_signapk_args",):
1844 OPTIONS.extra_signapk_args = shlex.split(a)
1845 elif o in ("--java_path",):
1846 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001847 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001848 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001849 elif o in ("--public_key_suffix",):
1850 OPTIONS.public_key_suffix = a
1851 elif o in ("--private_key_suffix",):
1852 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001853 elif o in ("--boot_signer_path",):
1854 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001855 elif o in ("--boot_signer_args",):
1856 OPTIONS.boot_signer_args = shlex.split(a)
1857 elif o in ("--verity_signer_path",):
1858 OPTIONS.verity_signer_path = a
1859 elif o in ("--verity_signer_args",):
1860 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001861 elif o in ("-s", "--device_specific"):
1862 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001863 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001864 key, value = a.split("=", 1)
1865 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001866 else:
1867 if extra_option_handler is None or not extra_option_handler(o, a):
1868 assert False, "unknown option \"%s\"" % (o,)
1869
Doug Zongker85448772014-09-09 14:59:20 -07001870 if OPTIONS.search_path:
1871 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1872 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001873
1874 return args
1875
1876
Tao Bao4c851b12016-09-19 13:54:38 -07001877def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001878 """Make a temp file and add it to the list of things to be deleted
1879 when Cleanup() is called. Return the filename."""
1880 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1881 os.close(fd)
1882 OPTIONS.tempfiles.append(fn)
1883 return fn
1884
1885
Tao Bao1c830bf2017-12-25 10:43:47 -08001886def MakeTempDir(prefix='tmp', suffix=''):
1887 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1888
1889 Returns:
1890 The absolute pathname of the new directory.
1891 """
1892 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1893 OPTIONS.tempfiles.append(dir_name)
1894 return dir_name
1895
1896
Doug Zongkereef39442009-04-02 12:14:19 -07001897def Cleanup():
1898 for i in OPTIONS.tempfiles:
1899 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001900 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001901 else:
1902 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001903 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001904
1905
1906class PasswordManager(object):
1907 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001908 self.editor = os.getenv("EDITOR")
1909 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001910
1911 def GetPasswords(self, items):
1912 """Get passwords corresponding to each string in 'items',
1913 returning a dict. (The dict may have keys in addition to the
1914 values in 'items'.)
1915
1916 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1917 user edit that file to add more needed passwords. If no editor is
1918 available, or $ANDROID_PW_FILE isn't define, prompts the user
1919 interactively in the ordinary way.
1920 """
1921
1922 current = self.ReadFile()
1923
1924 first = True
1925 while True:
1926 missing = []
1927 for i in items:
1928 if i not in current or not current[i]:
1929 missing.append(i)
1930 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001931 if not missing:
1932 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001933
1934 for i in missing:
1935 current[i] = ""
1936
1937 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001938 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001939 if sys.version_info[0] >= 3:
1940 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001941 answer = raw_input("try to edit again? [y]> ").strip()
1942 if answer and answer[0] not in 'yY':
1943 raise RuntimeError("key passwords unavailable")
1944 first = False
1945
1946 current = self.UpdateAndReadFile(current)
1947
Dan Albert8b72aef2015-03-23 19:13:21 -07001948 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001949 """Prompt the user to enter a value (password) for each key in
1950 'current' whose value is fales. Returns a new dict with all the
1951 values.
1952 """
1953 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001954 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001955 if v:
1956 result[k] = v
1957 else:
1958 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001959 result[k] = getpass.getpass(
1960 "Enter password for %s key> " % k).strip()
1961 if result[k]:
1962 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001963 return result
1964
1965 def UpdateAndReadFile(self, current):
1966 if not self.editor or not self.pwfile:
1967 return self.PromptResult(current)
1968
1969 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001970 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001971 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1972 f.write("# (Additional spaces are harmless.)\n\n")
1973
1974 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001975 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001976 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001977 f.write("[[[ %s ]]] %s\n" % (v, k))
1978 if not v and first_line is None:
1979 # position cursor on first line with no password.
1980 first_line = i + 4
1981 f.close()
1982
Tao Bao986ee862018-10-04 15:46:16 -07001983 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001984
1985 return self.ReadFile()
1986
1987 def ReadFile(self):
1988 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001989 if self.pwfile is None:
1990 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001991 try:
1992 f = open(self.pwfile, "r")
1993 for line in f:
1994 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001995 if not line or line[0] == '#':
1996 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001997 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1998 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001999 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002000 else:
2001 result[m.group(2)] = m.group(1)
2002 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002003 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002004 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002005 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002006 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002007
2008
Dan Albert8e0178d2015-01-27 15:53:15 -08002009def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2010 compress_type=None):
2011 import datetime
2012
2013 # http://b/18015246
2014 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2015 # for files larger than 2GiB. We can work around this by adjusting their
2016 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2017 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2018 # it isn't clear to me exactly what circumstances cause this).
2019 # `zipfile.write()` must be used directly to work around this.
2020 #
2021 # This mess can be avoided if we port to python3.
2022 saved_zip64_limit = zipfile.ZIP64_LIMIT
2023 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2024
2025 if compress_type is None:
2026 compress_type = zip_file.compression
2027 if arcname is None:
2028 arcname = filename
2029
2030 saved_stat = os.stat(filename)
2031
2032 try:
2033 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2034 # file to be zipped and reset it when we're done.
2035 os.chmod(filename, perms)
2036
2037 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002038 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2039 # intentional. zip stores datetimes in local time without a time zone
2040 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2041 # in the zip archive.
2042 local_epoch = datetime.datetime.fromtimestamp(0)
2043 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002044 os.utime(filename, (timestamp, timestamp))
2045
2046 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2047 finally:
2048 os.chmod(filename, saved_stat.st_mode)
2049 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2050 zipfile.ZIP64_LIMIT = saved_zip64_limit
2051
2052
Tao Bao58c1b962015-05-20 09:32:18 -07002053def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002054 compress_type=None):
2055 """Wrap zipfile.writestr() function to work around the zip64 limit.
2056
2057 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2058 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2059 when calling crc32(bytes).
2060
2061 But it still works fine to write a shorter string into a large zip file.
2062 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2063 when we know the string won't be too long.
2064 """
2065
2066 saved_zip64_limit = zipfile.ZIP64_LIMIT
2067 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2068
2069 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2070 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002071 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002072 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002073 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002074 else:
Tao Baof3282b42015-04-01 11:21:55 -07002075 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002076 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2077 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2078 # such a case (since
2079 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2080 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2081 # permission bits. We follow the logic in Python 3 to get consistent
2082 # behavior between using the two versions.
2083 if not zinfo.external_attr:
2084 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002085
2086 # If compress_type is given, it overrides the value in zinfo.
2087 if compress_type is not None:
2088 zinfo.compress_type = compress_type
2089
Tao Bao58c1b962015-05-20 09:32:18 -07002090 # If perms is given, it has a priority.
2091 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002092 # If perms doesn't set the file type, mark it as a regular file.
2093 if perms & 0o770000 == 0:
2094 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002095 zinfo.external_attr = perms << 16
2096
Tao Baof3282b42015-04-01 11:21:55 -07002097 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002098 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2099
Dan Albert8b72aef2015-03-23 19:13:21 -07002100 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002101 zipfile.ZIP64_LIMIT = saved_zip64_limit
2102
2103
Tao Bao89d7ab22017-12-14 17:05:33 -08002104def ZipDelete(zip_filename, entries):
2105 """Deletes entries from a ZIP file.
2106
2107 Since deleting entries from a ZIP file is not supported, it shells out to
2108 'zip -d'.
2109
2110 Args:
2111 zip_filename: The name of the ZIP file.
2112 entries: The name of the entry, or the list of names to be deleted.
2113
2114 Raises:
2115 AssertionError: In case of non-zero return from 'zip'.
2116 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002117 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002118 entries = [entries]
2119 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002120 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002121
2122
Tao Baof3282b42015-04-01 11:21:55 -07002123def ZipClose(zip_file):
2124 # http://b/18015246
2125 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2126 # central directory.
2127 saved_zip64_limit = zipfile.ZIP64_LIMIT
2128 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2129
2130 zip_file.close()
2131
2132 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002133
2134
2135class DeviceSpecificParams(object):
2136 module = None
2137 def __init__(self, **kwargs):
2138 """Keyword arguments to the constructor become attributes of this
2139 object, which is passed to all functions in the device-specific
2140 module."""
Tao Bao38884282019-07-10 22:20:56 -07002141 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002142 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002143 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002144
2145 if self.module is None:
2146 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002147 if not path:
2148 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002149 try:
2150 if os.path.isdir(path):
2151 info = imp.find_module("releasetools", [path])
2152 else:
2153 d, f = os.path.split(path)
2154 b, x = os.path.splitext(f)
2155 if x == ".py":
2156 f = b
2157 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002158 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002159 self.module = imp.load_module("device_specific", *info)
2160 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002161 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002162
2163 def _DoCall(self, function_name, *args, **kwargs):
2164 """Call the named function in the device-specific module, passing
2165 the given args and kwargs. The first argument to the call will be
2166 the DeviceSpecific object itself. If there is no module, or the
2167 module does not define the function, return the value of the
2168 'default' kwarg (which itself defaults to None)."""
2169 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002170 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002171 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2172
2173 def FullOTA_Assertions(self):
2174 """Called after emitting the block of assertions at the top of a
2175 full OTA package. Implementations can add whatever additional
2176 assertions they like."""
2177 return self._DoCall("FullOTA_Assertions")
2178
Doug Zongkere5ff5902012-01-17 10:55:37 -08002179 def FullOTA_InstallBegin(self):
2180 """Called at the start of full OTA installation."""
2181 return self._DoCall("FullOTA_InstallBegin")
2182
Yifan Hong10c530d2018-12-27 17:34:18 -08002183 def FullOTA_GetBlockDifferences(self):
2184 """Called during full OTA installation and verification.
2185 Implementation should return a list of BlockDifference objects describing
2186 the update on each additional partitions.
2187 """
2188 return self._DoCall("FullOTA_GetBlockDifferences")
2189
Doug Zongker05d3dea2009-06-22 11:32:31 -07002190 def FullOTA_InstallEnd(self):
2191 """Called at the end of full OTA installation; typically this is
2192 used to install the image for the device's baseband processor."""
2193 return self._DoCall("FullOTA_InstallEnd")
2194
2195 def IncrementalOTA_Assertions(self):
2196 """Called after emitting the block of assertions at the top of an
2197 incremental OTA package. Implementations can add whatever
2198 additional assertions they like."""
2199 return self._DoCall("IncrementalOTA_Assertions")
2200
Doug Zongkere5ff5902012-01-17 10:55:37 -08002201 def IncrementalOTA_VerifyBegin(self):
2202 """Called at the start of the verification phase of incremental
2203 OTA installation; additional checks can be placed here to abort
2204 the script before any changes are made."""
2205 return self._DoCall("IncrementalOTA_VerifyBegin")
2206
Doug Zongker05d3dea2009-06-22 11:32:31 -07002207 def IncrementalOTA_VerifyEnd(self):
2208 """Called at the end of the verification phase of incremental OTA
2209 installation; additional checks can be placed here to abort the
2210 script before any changes are made."""
2211 return self._DoCall("IncrementalOTA_VerifyEnd")
2212
Doug Zongkere5ff5902012-01-17 10:55:37 -08002213 def IncrementalOTA_InstallBegin(self):
2214 """Called at the start of incremental OTA installation (after
2215 verification is complete)."""
2216 return self._DoCall("IncrementalOTA_InstallBegin")
2217
Yifan Hong10c530d2018-12-27 17:34:18 -08002218 def IncrementalOTA_GetBlockDifferences(self):
2219 """Called during incremental OTA installation and verification.
2220 Implementation should return a list of BlockDifference objects describing
2221 the update on each additional partitions.
2222 """
2223 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2224
Doug Zongker05d3dea2009-06-22 11:32:31 -07002225 def IncrementalOTA_InstallEnd(self):
2226 """Called at the end of incremental OTA installation; typically
2227 this is used to install the image for the device's baseband
2228 processor."""
2229 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002230
Tao Bao9bc6bb22015-11-09 16:58:28 -08002231 def VerifyOTA_Assertions(self):
2232 return self._DoCall("VerifyOTA_Assertions")
2233
Tao Bao76def242017-11-21 09:25:31 -08002234
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002235class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002236 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002237 self.name = name
2238 self.data = data
2239 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002240 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002241 self.sha1 = sha1(data).hexdigest()
2242
2243 @classmethod
2244 def FromLocalFile(cls, name, diskname):
2245 f = open(diskname, "rb")
2246 data = f.read()
2247 f.close()
2248 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002249
2250 def WriteToTemp(self):
2251 t = tempfile.NamedTemporaryFile()
2252 t.write(self.data)
2253 t.flush()
2254 return t
2255
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002256 def WriteToDir(self, d):
2257 with open(os.path.join(d, self.name), "wb") as fp:
2258 fp.write(self.data)
2259
Geremy Condra36bd3652014-02-06 19:45:10 -08002260 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002261 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002262
Tao Bao76def242017-11-21 09:25:31 -08002263
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002264DIFF_PROGRAM_BY_EXT = {
2265 ".gz" : "imgdiff",
2266 ".zip" : ["imgdiff", "-z"],
2267 ".jar" : ["imgdiff", "-z"],
2268 ".apk" : ["imgdiff", "-z"],
2269 ".img" : "imgdiff",
2270 }
2271
Tao Bao76def242017-11-21 09:25:31 -08002272
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002273class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002274 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002275 self.tf = tf
2276 self.sf = sf
2277 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002278 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002279
2280 def ComputePatch(self):
2281 """Compute the patch (as a string of data) needed to turn sf into
2282 tf. Returns the same tuple as GetPatch()."""
2283
2284 tf = self.tf
2285 sf = self.sf
2286
Doug Zongker24cd2802012-08-14 16:36:15 -07002287 if self.diff_program:
2288 diff_program = self.diff_program
2289 else:
2290 ext = os.path.splitext(tf.name)[1]
2291 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002292
2293 ttemp = tf.WriteToTemp()
2294 stemp = sf.WriteToTemp()
2295
2296 ext = os.path.splitext(tf.name)[1]
2297
2298 try:
2299 ptemp = tempfile.NamedTemporaryFile()
2300 if isinstance(diff_program, list):
2301 cmd = copy.copy(diff_program)
2302 else:
2303 cmd = [diff_program]
2304 cmd.append(stemp.name)
2305 cmd.append(ttemp.name)
2306 cmd.append(ptemp.name)
2307 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002308 err = []
2309 def run():
2310 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002311 if e:
2312 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002313 th = threading.Thread(target=run)
2314 th.start()
2315 th.join(timeout=300) # 5 mins
2316 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002317 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002318 p.terminate()
2319 th.join(5)
2320 if th.is_alive():
2321 p.kill()
2322 th.join()
2323
Tianjie Xua2a9f992018-01-05 15:15:54 -08002324 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002325 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002326 self.patch = None
2327 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002328 diff = ptemp.read()
2329 finally:
2330 ptemp.close()
2331 stemp.close()
2332 ttemp.close()
2333
2334 self.patch = diff
2335 return self.tf, self.sf, self.patch
2336
2337
2338 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002339 """Returns a tuple of (target_file, source_file, patch_data).
2340
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002341 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002342 computing the patch failed.
2343 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002344 return self.tf, self.sf, self.patch
2345
2346
2347def ComputeDifferences(diffs):
2348 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002349 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002350
2351 # Do the largest files first, to try and reduce the long-pole effect.
2352 by_size = [(i.tf.size, i) for i in diffs]
2353 by_size.sort(reverse=True)
2354 by_size = [i[1] for i in by_size]
2355
2356 lock = threading.Lock()
2357 diff_iter = iter(by_size) # accessed under lock
2358
2359 def worker():
2360 try:
2361 lock.acquire()
2362 for d in diff_iter:
2363 lock.release()
2364 start = time.time()
2365 d.ComputePatch()
2366 dur = time.time() - start
2367 lock.acquire()
2368
2369 tf, sf, patch = d.GetPatch()
2370 if sf.name == tf.name:
2371 name = tf.name
2372 else:
2373 name = "%s (%s)" % (tf.name, sf.name)
2374 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002375 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002376 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002377 logger.info(
2378 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2379 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002380 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002381 except Exception:
2382 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002383 raise
2384
2385 # start worker threads; wait for them all to finish.
2386 threads = [threading.Thread(target=worker)
2387 for i in range(OPTIONS.worker_threads)]
2388 for th in threads:
2389 th.start()
2390 while threads:
2391 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002392
2393
Dan Albert8b72aef2015-03-23 19:13:21 -07002394class BlockDifference(object):
2395 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002396 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002397 self.tgt = tgt
2398 self.src = src
2399 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002400 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002401 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002402
Tao Baodd2a5892015-03-12 12:32:37 -07002403 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002404 version = max(
2405 int(i) for i in
2406 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002407 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002408 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002409
Tianjie Xu41976c72019-07-03 13:57:01 -07002410 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2411 version=self.version,
2412 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002413 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002414 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002415 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002416 self.touched_src_ranges = b.touched_src_ranges
2417 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002418
Yifan Hong10c530d2018-12-27 17:34:18 -08002419 # On devices with dynamic partitions, for new partitions,
2420 # src is None but OPTIONS.source_info_dict is not.
2421 if OPTIONS.source_info_dict is None:
2422 is_dynamic_build = OPTIONS.info_dict.get(
2423 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002424 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002425 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002426 is_dynamic_build = OPTIONS.source_info_dict.get(
2427 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002428 is_dynamic_source = partition in shlex.split(
2429 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002430
Yifan Hongbb2658d2019-01-25 12:30:58 -08002431 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002432 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2433
Yifan Hongbb2658d2019-01-25 12:30:58 -08002434 # For dynamic partitions builds, check partition list in both source
2435 # and target build because new partitions may be added, and existing
2436 # partitions may be removed.
2437 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2438
Yifan Hong10c530d2018-12-27 17:34:18 -08002439 if is_dynamic:
2440 self.device = 'map_partition("%s")' % partition
2441 else:
2442 if OPTIONS.source_info_dict is None:
2443 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2444 else:
2445 _, device_path = GetTypeAndDevice("/" + partition,
2446 OPTIONS.source_info_dict)
2447 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002448
Tao Baod8d14be2016-02-04 14:26:02 -08002449 @property
2450 def required_cache(self):
2451 return self._required_cache
2452
Tao Bao76def242017-11-21 09:25:31 -08002453 def WriteScript(self, script, output_zip, progress=None,
2454 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002455 if not self.src:
2456 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002457 script.Print("Patching %s image unconditionally..." % (self.partition,))
2458 else:
2459 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002460
Dan Albert8b72aef2015-03-23 19:13:21 -07002461 if progress:
2462 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002463 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002464
2465 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002466 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002467
Tao Bao9bc6bb22015-11-09 16:58:28 -08002468 def WriteStrictVerifyScript(self, script):
2469 """Verify all the blocks in the care_map, including clobbered blocks.
2470
2471 This differs from the WriteVerifyScript() function: a) it prints different
2472 error messages; b) it doesn't allow half-way updated images to pass the
2473 verification."""
2474
2475 partition = self.partition
2476 script.Print("Verifying %s..." % (partition,))
2477 ranges = self.tgt.care_map
2478 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002479 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002480 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2481 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002482 self.device, ranges_str,
2483 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002484 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002485 script.AppendExtra("")
2486
Tao Baod522bdc2016-04-12 15:53:16 -07002487 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002488 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002489
2490 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002491 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002492 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002493
2494 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002495 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002496 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002497 ranges = self.touched_src_ranges
2498 expected_sha1 = self.touched_src_sha1
2499 else:
2500 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2501 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002502
2503 # No blocks to be checked, skipping.
2504 if not ranges:
2505 return
2506
Tao Bao5ece99d2015-05-12 11:42:31 -07002507 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002508 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002509 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002510 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2511 '"%s.patch.dat")) then' % (
2512 self.device, ranges_str, expected_sha1,
2513 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002514 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002515 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002516
Tianjie Xufc3422a2015-12-15 11:53:59 -08002517 if self.version >= 4:
2518
2519 # Bug: 21124327
2520 # When generating incrementals for the system and vendor partitions in
2521 # version 4 or newer, explicitly check the first block (which contains
2522 # the superblock) of the partition to see if it's what we expect. If
2523 # this check fails, give an explicit log message about the partition
2524 # having been remounted R/W (the most likely explanation).
2525 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002526 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002527
2528 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002529 if partition == "system":
2530 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2531 else:
2532 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002533 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002534 'ifelse (block_image_recover({device}, "{ranges}") && '
2535 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002536 'package_extract_file("{partition}.transfer.list"), '
2537 '"{partition}.new.dat", "{partition}.patch.dat"), '
2538 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002539 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002540 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002541 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002542
Tao Baodd2a5892015-03-12 12:32:37 -07002543 # Abort the OTA update. Note that the incremental OTA cannot be applied
2544 # even if it may match the checksum of the target partition.
2545 # a) If version < 3, operations like move and erase will make changes
2546 # unconditionally and damage the partition.
2547 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002548 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002549 if partition == "system":
2550 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2551 else:
2552 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2553 script.AppendExtra((
2554 'abort("E%d: %s partition has unexpected contents");\n'
2555 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002556
Yifan Hong10c530d2018-12-27 17:34:18 -08002557 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002558 partition = self.partition
2559 script.Print('Verifying the updated %s image...' % (partition,))
2560 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2561 ranges = self.tgt.care_map
2562 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002563 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002564 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002565 self.device, ranges_str,
2566 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002567
2568 # Bug: 20881595
2569 # Verify that extended blocks are really zeroed out.
2570 if self.tgt.extended:
2571 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002572 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002573 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002574 self.device, ranges_str,
2575 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002576 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002577 if partition == "system":
2578 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2579 else:
2580 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002581 script.AppendExtra(
2582 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002583 ' abort("E%d: %s partition has unexpected non-zero contents after '
2584 'OTA update");\n'
2585 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002586 else:
2587 script.Print('Verified the updated %s image.' % (partition,))
2588
Tianjie Xu209db462016-05-24 17:34:52 -07002589 if partition == "system":
2590 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2591 else:
2592 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2593
Tao Bao5fcaaef2015-06-01 13:40:49 -07002594 script.AppendExtra(
2595 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002596 ' abort("E%d: %s partition has unexpected contents after OTA '
2597 'update");\n'
2598 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002599
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002600 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002601 ZipWrite(output_zip,
2602 '{}.transfer.list'.format(self.path),
2603 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002604
Tao Bao76def242017-11-21 09:25:31 -08002605 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2606 # its size. Quailty 9 almost triples the compression time but doesn't
2607 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002608 # zip | brotli(quality 6) | brotli(quality 9)
2609 # compressed_size: 942M | 869M (~8% reduced) | 854M
2610 # compression_time: 75s | 265s | 719s
2611 # decompression_time: 15s | 25s | 25s
2612
2613 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002614 brotli_cmd = ['brotli', '--quality=6',
2615 '--output={}.new.dat.br'.format(self.path),
2616 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002617 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002618 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002619
2620 new_data_name = '{}.new.dat.br'.format(self.partition)
2621 ZipWrite(output_zip,
2622 '{}.new.dat.br'.format(self.path),
2623 new_data_name,
2624 compress_type=zipfile.ZIP_STORED)
2625 else:
2626 new_data_name = '{}.new.dat'.format(self.partition)
2627 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2628
Dan Albert8e0178d2015-01-27 15:53:15 -08002629 ZipWrite(output_zip,
2630 '{}.patch.dat'.format(self.path),
2631 '{}.patch.dat'.format(self.partition),
2632 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002633
Tianjie Xu209db462016-05-24 17:34:52 -07002634 if self.partition == "system":
2635 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2636 else:
2637 code = ErrorCode.VENDOR_UPDATE_FAILURE
2638
Yifan Hong10c530d2018-12-27 17:34:18 -08002639 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002640 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002641 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002642 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002643 device=self.device, partition=self.partition,
2644 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002645 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002646
Dan Albert8b72aef2015-03-23 19:13:21 -07002647 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002648 data = source.ReadRangeSet(ranges)
2649 ctx = sha1()
2650
2651 for p in data:
2652 ctx.update(p)
2653
2654 return ctx.hexdigest()
2655
Tao Baoe9b61912015-07-09 17:37:49 -07002656 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2657 """Return the hash value for all zero blocks."""
2658 zero_block = '\x00' * 4096
2659 ctx = sha1()
2660 for _ in range(num_blocks):
2661 ctx.update(zero_block)
2662
2663 return ctx.hexdigest()
2664
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002665
Tianjie Xu41976c72019-07-03 13:57:01 -07002666# Expose these two classes to support vendor-specific scripts
2667DataImage = images.DataImage
2668EmptyImage = images.EmptyImage
2669
Tao Bao76def242017-11-21 09:25:31 -08002670
Doug Zongker96a57e72010-09-26 14:57:41 -07002671# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002672PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002673 "ext4": "EMMC",
2674 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002675 "f2fs": "EMMC",
2676 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002677}
Doug Zongker96a57e72010-09-26 14:57:41 -07002678
Tao Bao76def242017-11-21 09:25:31 -08002679
Doug Zongker96a57e72010-09-26 14:57:41 -07002680def GetTypeAndDevice(mount_point, info):
2681 fstab = info["fstab"]
2682 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002683 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2684 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002685 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002686 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002687
2688
2689def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002690 """Parses and converts a PEM-encoded certificate into DER-encoded.
2691
2692 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2693
2694 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002695 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002696 """
2697 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002698 save = False
2699 for line in data.split("\n"):
2700 if "--END CERTIFICATE--" in line:
2701 break
2702 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002703 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002704 if "--BEGIN CERTIFICATE--" in line:
2705 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002706 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002707 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002708
Tao Bao04e1f012018-02-04 12:13:35 -08002709
2710def ExtractPublicKey(cert):
2711 """Extracts the public key (PEM-encoded) from the given certificate file.
2712
2713 Args:
2714 cert: The certificate filename.
2715
2716 Returns:
2717 The public key string.
2718
2719 Raises:
2720 AssertionError: On non-zero return from 'openssl'.
2721 """
2722 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2723 # While openssl 1.1 writes the key into the given filename followed by '-out',
2724 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2725 # stdout instead.
2726 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2727 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2728 pubkey, stderrdata = proc.communicate()
2729 assert proc.returncode == 0, \
2730 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2731 return pubkey
2732
2733
Tao Bao1ac886e2019-06-26 11:58:22 -07002734def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002735 """Extracts the AVB public key from the given public or private key.
2736
2737 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002738 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002739 key: The input key file, which should be PEM-encoded public or private key.
2740
2741 Returns:
2742 The path to the extracted AVB public key file.
2743 """
2744 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2745 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002746 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002747 return output
2748
2749
Doug Zongker412c02f2014-02-13 10:58:24 -08002750def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2751 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002752 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002753
Tao Bao6d5d6232018-03-09 17:04:42 -08002754 Most of the space in the boot and recovery images is just the kernel, which is
2755 identical for the two, so the resulting patch should be efficient. Add it to
2756 the output zip, along with a shell script that is run from init.rc on first
2757 boot to actually do the patching and install the new recovery image.
2758
2759 Args:
2760 input_dir: The top-level input directory of the target-files.zip.
2761 output_sink: The callback function that writes the result.
2762 recovery_img: File object for the recovery image.
2763 boot_img: File objects for the boot image.
2764 info_dict: A dict returned by common.LoadInfoDict() on the input
2765 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002766 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002767 if info_dict is None:
2768 info_dict = OPTIONS.info_dict
2769
Tao Bao6d5d6232018-03-09 17:04:42 -08002770 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002771 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2772
2773 if board_uses_vendorimage:
2774 # In this case, the output sink is rooted at VENDOR
2775 recovery_img_path = "etc/recovery.img"
2776 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2777 sh_dir = "bin"
2778 else:
2779 # In this case the output sink is rooted at SYSTEM
2780 recovery_img_path = "vendor/etc/recovery.img"
2781 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2782 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002783
Tao Baof2cffbd2015-07-22 12:33:18 -07002784 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002785 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002786
2787 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002788 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002789 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002790 # With system-root-image, boot and recovery images will have mismatching
2791 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2792 # to handle such a case.
2793 if system_root_image:
2794 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002795 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002796 assert not os.path.exists(path)
2797 else:
2798 diff_program = ["imgdiff"]
2799 if os.path.exists(path):
2800 diff_program.append("-b")
2801 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002802 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002803 else:
2804 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002805
2806 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2807 _, _, patch = d.ComputePatch()
2808 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002809
Dan Albertebb19aa2015-03-27 19:11:53 -07002810 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002811 # The following GetTypeAndDevice()s need to use the path in the target
2812 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002813 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2814 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2815 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002816 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002817
Tao Baof2cffbd2015-07-22 12:33:18 -07002818 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002819
2820 # Note that we use /vendor to refer to the recovery resources. This will
2821 # work for a separate vendor partition mounted at /vendor or a
2822 # /system/vendor subdirectory on the system partition, for which init will
2823 # create a symlink from /vendor to /system/vendor.
2824
2825 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002826if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2827 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002828 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002829 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2830 log -t recovery "Installing new recovery image: succeeded" || \\
2831 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002832else
2833 log -t recovery "Recovery image already installed"
2834fi
2835""" % {'type': recovery_type,
2836 'device': recovery_device,
2837 'sha1': recovery_img.sha1,
2838 'size': recovery_img.size}
2839 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002840 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002841if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2842 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002843 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002844 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2845 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2846 log -t recovery "Installing new recovery image: succeeded" || \\
2847 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002848else
2849 log -t recovery "Recovery image already installed"
2850fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002851""" % {'boot_size': boot_img.size,
2852 'boot_sha1': boot_img.sha1,
2853 'recovery_size': recovery_img.size,
2854 'recovery_sha1': recovery_img.sha1,
2855 'boot_type': boot_type,
2856 'boot_device': boot_device,
2857 'recovery_type': recovery_type,
2858 'recovery_device': recovery_device,
2859 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002860
Bill Peckhame868aec2019-09-17 17:06:47 -07002861 # The install script location moved from /system/etc to /system/bin in the L
2862 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2863 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002864
Tao Bao32fcdab2018-10-12 10:30:39 -07002865 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002866
Tao Baoda30cfa2017-12-01 16:19:46 -08002867 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002868
2869
2870class DynamicPartitionUpdate(object):
2871 def __init__(self, src_group=None, tgt_group=None, progress=None,
2872 block_difference=None):
2873 self.src_group = src_group
2874 self.tgt_group = tgt_group
2875 self.progress = progress
2876 self.block_difference = block_difference
2877
2878 @property
2879 def src_size(self):
2880 if not self.block_difference:
2881 return 0
2882 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2883
2884 @property
2885 def tgt_size(self):
2886 if not self.block_difference:
2887 return 0
2888 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2889
2890 @staticmethod
2891 def _GetSparseImageSize(img):
2892 if not img:
2893 return 0
2894 return img.blocksize * img.total_blocks
2895
2896
2897class DynamicGroupUpdate(object):
2898 def __init__(self, src_size=None, tgt_size=None):
2899 # None: group does not exist. 0: no size limits.
2900 self.src_size = src_size
2901 self.tgt_size = tgt_size
2902
2903
2904class DynamicPartitionsDifference(object):
2905 def __init__(self, info_dict, block_diffs, progress_dict=None,
2906 source_info_dict=None):
2907 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002908 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002909
2910 self._remove_all_before_apply = False
2911 if source_info_dict is None:
2912 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002913 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002914
Tao Baof1113e92019-06-18 12:10:14 -07002915 block_diff_dict = collections.OrderedDict(
2916 [(e.partition, e) for e in block_diffs])
2917
Yifan Hong10c530d2018-12-27 17:34:18 -08002918 assert len(block_diff_dict) == len(block_diffs), \
2919 "Duplicated BlockDifference object for {}".format(
2920 [partition for partition, count in
2921 collections.Counter(e.partition for e in block_diffs).items()
2922 if count > 1])
2923
Yifan Hong79997e52019-01-23 16:56:19 -08002924 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002925
2926 for p, block_diff in block_diff_dict.items():
2927 self._partition_updates[p] = DynamicPartitionUpdate()
2928 self._partition_updates[p].block_difference = block_diff
2929
2930 for p, progress in progress_dict.items():
2931 if p in self._partition_updates:
2932 self._partition_updates[p].progress = progress
2933
2934 tgt_groups = shlex.split(info_dict.get(
2935 "super_partition_groups", "").strip())
2936 src_groups = shlex.split(source_info_dict.get(
2937 "super_partition_groups", "").strip())
2938
2939 for g in tgt_groups:
2940 for p in shlex.split(info_dict.get(
2941 "super_%s_partition_list" % g, "").strip()):
2942 assert p in self._partition_updates, \
2943 "{} is in target super_{}_partition_list but no BlockDifference " \
2944 "object is provided.".format(p, g)
2945 self._partition_updates[p].tgt_group = g
2946
2947 for g in src_groups:
2948 for p in shlex.split(source_info_dict.get(
2949 "super_%s_partition_list" % g, "").strip()):
2950 assert p in self._partition_updates, \
2951 "{} is in source super_{}_partition_list but no BlockDifference " \
2952 "object is provided.".format(p, g)
2953 self._partition_updates[p].src_group = g
2954
Yifan Hong45433e42019-01-18 13:55:25 -08002955 target_dynamic_partitions = set(shlex.split(info_dict.get(
2956 "dynamic_partition_list", "").strip()))
2957 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2958 if u.tgt_size)
2959 assert block_diffs_with_target == target_dynamic_partitions, \
2960 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2961 list(target_dynamic_partitions), list(block_diffs_with_target))
2962
2963 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2964 "dynamic_partition_list", "").strip()))
2965 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2966 if u.src_size)
2967 assert block_diffs_with_source == source_dynamic_partitions, \
2968 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2969 list(source_dynamic_partitions), list(block_diffs_with_source))
2970
Yifan Hong10c530d2018-12-27 17:34:18 -08002971 if self._partition_updates:
2972 logger.info("Updating dynamic partitions %s",
2973 self._partition_updates.keys())
2974
Yifan Hong79997e52019-01-23 16:56:19 -08002975 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002976
2977 for g in tgt_groups:
2978 self._group_updates[g] = DynamicGroupUpdate()
2979 self._group_updates[g].tgt_size = int(info_dict.get(
2980 "super_%s_group_size" % g, "0").strip())
2981
2982 for g in src_groups:
2983 if g not in self._group_updates:
2984 self._group_updates[g] = DynamicGroupUpdate()
2985 self._group_updates[g].src_size = int(source_info_dict.get(
2986 "super_%s_group_size" % g, "0").strip())
2987
2988 self._Compute()
2989
2990 def WriteScript(self, script, output_zip, write_verify_script=False):
2991 script.Comment('--- Start patching dynamic partitions ---')
2992 for p, u in self._partition_updates.items():
2993 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2994 script.Comment('Patch partition %s' % p)
2995 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2996 write_verify_script=False)
2997
2998 op_list_path = MakeTempFile()
2999 with open(op_list_path, 'w') as f:
3000 for line in self._op_list:
3001 f.write('{}\n'.format(line))
3002
3003 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3004
3005 script.Comment('Update dynamic partition metadata')
3006 script.AppendExtra('assert(update_dynamic_partitions('
3007 'package_extract_file("dynamic_partitions_op_list")));')
3008
3009 if write_verify_script:
3010 for p, u in self._partition_updates.items():
3011 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3012 u.block_difference.WritePostInstallVerifyScript(script)
3013 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3014
3015 for p, u in self._partition_updates.items():
3016 if u.tgt_size and u.src_size <= u.tgt_size:
3017 script.Comment('Patch partition %s' % p)
3018 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3019 write_verify_script=write_verify_script)
3020 if write_verify_script:
3021 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3022
3023 script.Comment('--- End patching dynamic partitions ---')
3024
3025 def _Compute(self):
3026 self._op_list = list()
3027
3028 def append(line):
3029 self._op_list.append(line)
3030
3031 def comment(line):
3032 self._op_list.append("# %s" % line)
3033
3034 if self._remove_all_before_apply:
3035 comment('Remove all existing dynamic partitions and groups before '
3036 'applying full OTA')
3037 append('remove_all_groups')
3038
3039 for p, u in self._partition_updates.items():
3040 if u.src_group and not u.tgt_group:
3041 append('remove %s' % p)
3042
3043 for p, u in self._partition_updates.items():
3044 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3045 comment('Move partition %s from %s to default' % (p, u.src_group))
3046 append('move %s default' % p)
3047
3048 for p, u in self._partition_updates.items():
3049 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3050 comment('Shrink partition %s from %d to %d' %
3051 (p, u.src_size, u.tgt_size))
3052 append('resize %s %s' % (p, u.tgt_size))
3053
3054 for g, u in self._group_updates.items():
3055 if u.src_size is not None and u.tgt_size is None:
3056 append('remove_group %s' % g)
3057 if (u.src_size is not None and u.tgt_size is not None and
3058 u.src_size > u.tgt_size):
3059 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3060 append('resize_group %s %d' % (g, u.tgt_size))
3061
3062 for g, u in self._group_updates.items():
3063 if u.src_size is None and u.tgt_size is not None:
3064 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3065 append('add_group %s %d' % (g, u.tgt_size))
3066 if (u.src_size is not None and u.tgt_size is not None and
3067 u.src_size < u.tgt_size):
3068 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3069 append('resize_group %s %d' % (g, u.tgt_size))
3070
3071 for p, u in self._partition_updates.items():
3072 if u.tgt_group and not u.src_group:
3073 comment('Add partition %s to group %s' % (p, u.tgt_group))
3074 append('add %s %s' % (p, u.tgt_group))
3075
3076 for p, u in self._partition_updates.items():
3077 if u.tgt_size and u.src_size < u.tgt_size:
3078 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3079 append('resize %s %d' % (p, u.tgt_size))
3080
3081 for p, u in self._partition_updates.items():
3082 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3083 comment('Move partition %s from default to %s' %
3084 (p, u.tgt_group))
3085 append('move %s %s' % (p, u.tgt_group))