blob: 3ba9a236cb7cdf60c1889ca7ac4acf024099557b [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):
Tao Baoafd92a82019-10-10 22:44:22 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070052 # Set up search path, in order to find framework/ and lib64/. At the time of
53 # running this function, user-supplied search path (`--path`) hasn't been
54 # available. So the value set here is the default, which might be overridden
55 # by commandline flag later.
56 exec_path = sys.argv[0]
57 if exec_path.endswith('.py'):
58 script_name = os.path.basename(exec_path)
59 # logger hasn't been initialized yet at this point. Use print to output
60 # warnings.
61 print(
62 'Warning: releasetools script should be invoked as hermetic Python '
63 'executable -- build and run `{}` directly.'.format(script_name[:-3]),
64 file=sys.stderr)
65 self.search_path = os.path.realpath(os.path.join(exec_path, '..'))
Pavel Salomatov32676552019-03-06 20:00:45 +030066
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080068 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.extra_signapk_args = []
70 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080071 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070072 self.public_key_suffix = ".x509.pem"
73 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070074 # use otatools built boot_signer by default
75 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070076 self.boot_signer_args = []
77 self.verity_signer_path = None
78 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070079 self.verbose = False
80 self.tempfiles = []
81 self.device_specific = None
82 self.extras = {}
83 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070084 self.source_info_dict = None
85 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070086 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070087 # Stash size cannot exceed cache_size * threshold.
88 self.cache_size = None
89 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070090 self.logfile = None
Dan Albert8b72aef2015-03-23 19:13:21 -070091
92
93OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070094
Tao Bao71197512018-10-11 14:08:45 -070095# The block size that's used across the releasetools scripts.
96BLOCK_SIZE = 4096
97
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080098# Values for "certificate" in apkcerts that mean special things.
99SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
100
Tao Bao5cc0abb2019-03-21 10:18:05 -0700101# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
102# that system_other is not in the list because we don't want to include its
103# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900104AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Steve Mucklee1b10862019-07-10 10:49:37 -0700105 'system_ext', 'vendor', 'vendor_boot')
Tao Bao9dd909e2017-11-14 11:27:32 -0800106
Tao Bao08c190f2019-06-03 23:07:58 -0700107# Chained VBMeta partitions.
108AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
109
Tianjie Xu861f4132018-09-12 11:49:33 -0700110# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900111PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700112
113
Tianjie Xu209db462016-05-24 17:34:52 -0700114class ErrorCode(object):
115 """Define error_codes for failures that happen during the actual
116 update package installation.
117
118 Error codes 0-999 are reserved for failures before the package
119 installation (i.e. low battery, package verification failure).
120 Detailed code in 'bootable/recovery/error_code.h' """
121
122 SYSTEM_VERIFICATION_FAILURE = 1000
123 SYSTEM_UPDATE_FAILURE = 1001
124 SYSTEM_UNEXPECTED_CONTENTS = 1002
125 SYSTEM_NONZERO_CONTENTS = 1003
126 SYSTEM_RECOVER_FAILURE = 1004
127 VENDOR_VERIFICATION_FAILURE = 2000
128 VENDOR_UPDATE_FAILURE = 2001
129 VENDOR_UNEXPECTED_CONTENTS = 2002
130 VENDOR_NONZERO_CONTENTS = 2003
131 VENDOR_RECOVER_FAILURE = 2004
132 OEM_PROP_MISMATCH = 3000
133 FINGERPRINT_MISMATCH = 3001
134 THUMBPRINT_MISMATCH = 3002
135 OLDER_BUILD = 3003
136 DEVICE_MISMATCH = 3004
137 BAD_PATCH_FILE = 3005
138 INSUFFICIENT_CACHE_SPACE = 3006
139 TUNE_PARTITION_FAILURE = 3007
140 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800141
Tao Bao80921982018-03-21 21:02:19 -0700142
Dan Albert8b72aef2015-03-23 19:13:21 -0700143class ExternalError(RuntimeError):
144 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700145
146
Tao Bao32fcdab2018-10-12 10:30:39 -0700147def InitLogging():
148 DEFAULT_LOGGING_CONFIG = {
149 'version': 1,
150 'disable_existing_loggers': False,
151 'formatters': {
152 'standard': {
153 'format':
154 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
155 'datefmt': '%Y-%m-%d %H:%M:%S',
156 },
157 },
158 'handlers': {
159 'default': {
160 'class': 'logging.StreamHandler',
161 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700162 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700163 },
164 },
165 'loggers': {
166 '': {
167 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700168 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700169 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700170 }
171 }
172 }
173 env_config = os.getenv('LOGGING_CONFIG')
174 if env_config:
175 with open(env_config) as f:
176 config = json.load(f)
177 else:
178 config = DEFAULT_LOGGING_CONFIG
179
180 # Increase the logging level for verbose mode.
181 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700182 config = copy.deepcopy(config)
183 config['handlers']['default']['level'] = 'INFO'
184
185 if OPTIONS.logfile:
186 config = copy.deepcopy(config)
187 config['handlers']['logfile'] = {
188 'class': 'logging.FileHandler',
189 'formatter': 'standard',
190 'level': 'INFO',
191 'mode': 'w',
192 'filename': OPTIONS.logfile,
193 }
194 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700195
196 logging.config.dictConfig(config)
197
198
Tao Bao39451582017-05-04 11:10:47 -0700199def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700200 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700201
Tao Bao73dd4f42018-10-04 16:25:33 -0700202 Args:
203 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700204 verbose: Whether the commands should be shown. Default to the global
205 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700206 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
207 stdin, etc. stdout and stderr will default to subprocess.PIPE and
208 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800209 universal_newlines will default to True, as most of the users in
210 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700211
212 Returns:
213 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700214 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700215 if 'stdout' not in kwargs and 'stderr' not in kwargs:
216 kwargs['stdout'] = subprocess.PIPE
217 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800218 if 'universal_newlines' not in kwargs:
219 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700220 # Don't log any if caller explicitly says so.
221 if verbose != False:
222 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700223 return subprocess.Popen(args, **kwargs)
224
225
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800226def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800227 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800228
229 Args:
230 args: The command represented as a list of strings.
231 verbose: Whether the commands should be shown. Default to the global
232 verbosity if unspecified.
233 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
234 stdin, etc. stdout and stderr will default to subprocess.PIPE and
235 subprocess.STDOUT respectively unless caller specifies any of them.
236
Bill Peckham889b0c62019-02-21 18:53:37 -0800237 Raises:
238 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800239 """
240 proc = Run(args, verbose=verbose, **kwargs)
241 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800242
243 if proc.returncode != 0:
244 raise ExternalError(
245 "Failed to run command '{}' (exit code {})".format(
246 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800247
248
Tao Bao986ee862018-10-04 15:46:16 -0700249def RunAndCheckOutput(args, verbose=None, **kwargs):
250 """Runs the given command and returns the output.
251
252 Args:
253 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700254 verbose: Whether the commands should be shown. Default to the global
255 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700256 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
257 stdin, etc. stdout and stderr will default to subprocess.PIPE and
258 subprocess.STDOUT respectively unless caller specifies any of them.
259
260 Returns:
261 The output string.
262
263 Raises:
264 ExternalError: On non-zero exit from the command.
265 """
Tao Bao986ee862018-10-04 15:46:16 -0700266 proc = Run(args, verbose=verbose, **kwargs)
267 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800268 if output is None:
269 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700270 # Don't log any if caller explicitly says so.
271 if verbose != False:
272 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700273 if proc.returncode != 0:
274 raise ExternalError(
275 "Failed to run command '{}' (exit code {}):\n{}".format(
276 args, proc.returncode, output))
277 return output
278
279
Tao Baoc765cca2018-01-31 17:32:40 -0800280def RoundUpTo4K(value):
281 rounded_up = value + 4095
282 return rounded_up - (rounded_up % 4096)
283
284
Ying Wang7e6d4e42010-12-13 16:25:36 -0800285def CloseInheritedPipes():
286 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
287 before doing other work."""
288 if platform.system() != "Darwin":
289 return
290 for d in range(3, 1025):
291 try:
292 stat = os.fstat(d)
293 if stat is not None:
294 pipebit = stat[0] & 0x1000
295 if pipebit != 0:
296 os.close(d)
297 except OSError:
298 pass
299
300
Tao Bao1c320f82019-10-04 23:25:12 -0700301class BuildInfo(object):
302 """A class that holds the information for a given build.
303
304 This class wraps up the property querying for a given source or target build.
305 It abstracts away the logic of handling OEM-specific properties, and caches
306 the commonly used properties such as fingerprint.
307
308 There are two types of info dicts: a) build-time info dict, which is generated
309 at build time (i.e. included in a target_files zip); b) OEM info dict that is
310 specified at package generation time (via command line argument
311 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
312 having "oem_fingerprint_properties" in build-time info dict), all the queries
313 would be answered based on build-time info dict only. Otherwise if using
314 OEM-specific properties, some of them will be calculated from two info dicts.
315
316 Users can query properties similarly as using a dict() (e.g. info['fstab']),
317 or to query build properties via GetBuildProp() or GetVendorBuildProp().
318
319 Attributes:
320 info_dict: The build-time info dict.
321 is_ab: Whether it's a build that uses A/B OTA.
322 oem_dicts: A list of OEM dicts.
323 oem_props: A list of OEM properties that should be read from OEM dicts; None
324 if the build doesn't use any OEM-specific property.
325 fingerprint: The fingerprint of the build, which would be calculated based
326 on OEM properties if applicable.
327 device: The device name, which could come from OEM dicts if applicable.
328 """
329
330 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
331 "ro.product.manufacturer", "ro.product.model",
332 "ro.product.name"]
333 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
334 "system_ext", "system"]
335
Tao Bao3ed35d32019-10-07 20:48:48 -0700336 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700337 """Initializes a BuildInfo instance with the given dicts.
338
339 Note that it only wraps up the given dicts, without making copies.
340
341 Arguments:
342 info_dict: The build-time info dict.
343 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
344 that it always uses the first dict to calculate the fingerprint or the
345 device name. The rest would be used for asserting OEM properties only
346 (e.g. one package can be installed on one of these devices).
347
348 Raises:
349 ValueError: On invalid inputs.
350 """
351 self.info_dict = info_dict
352 self.oem_dicts = oem_dicts
353
354 self._is_ab = info_dict.get("ab_update") == "true"
355 self._oem_props = info_dict.get("oem_fingerprint_properties")
356
357 if self._oem_props:
358 assert oem_dicts, "OEM source required for this build"
359
360 # These two should be computed only after setting self._oem_props.
361 self._device = self.GetOemProperty("ro.product.device")
362 self._fingerprint = self.CalculateFingerprint()
363
364 # Sanity check the build fingerprint.
365 if (' ' in self._fingerprint or
366 any(ord(ch) > 127 for ch in self._fingerprint)):
367 raise ValueError(
368 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
369 '3.2.2. Build Parameters.'.format(self._fingerprint))
370
371 @property
372 def is_ab(self):
373 return self._is_ab
374
375 @property
376 def device(self):
377 return self._device
378
379 @property
380 def fingerprint(self):
381 return self._fingerprint
382
383 @property
384 def vendor_fingerprint(self):
385 return self._fingerprint_of("vendor")
386
387 @property
388 def product_fingerprint(self):
389 return self._fingerprint_of("product")
390
391 @property
392 def odm_fingerprint(self):
393 return self._fingerprint_of("odm")
394
395 def _fingerprint_of(self, partition):
396 if partition + ".build.prop" not in self.info_dict:
397 return None
398 build_prop = self.info_dict[partition + ".build.prop"]
399 if "ro." + partition + ".build.fingerprint" in build_prop:
400 return build_prop["ro." + partition + ".build.fingerprint"]
401 if "ro." + partition + ".build.thumbprint" in build_prop:
402 return build_prop["ro." + partition + ".build.thumbprint"]
403 return None
404
405 @property
406 def oem_props(self):
407 return self._oem_props
408
409 def __getitem__(self, key):
410 return self.info_dict[key]
411
412 def __setitem__(self, key, value):
413 self.info_dict[key] = value
414
415 def get(self, key, default=None):
416 return self.info_dict.get(key, default)
417
418 def items(self):
419 return self.info_dict.items()
420
421 def GetBuildProp(self, prop):
422 """Returns the inquired build property."""
423 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
424 return self._ResolveRoProductBuildProp(prop)
425
426 try:
427 return self.info_dict.get("build.prop", {})[prop]
428 except KeyError:
429 raise ExternalError("couldn't find %s in build.prop" % (prop,))
430
431 def _ResolveRoProductBuildProp(self, prop):
432 """Resolves the inquired ro.product.* build property"""
433 prop_val = self.info_dict.get("build.prop", {}).get(prop)
434 if prop_val:
435 return prop_val
436
437 source_order_val = self.info_dict.get("build.prop", {}).get(
438 "ro.product.property_source_order")
439 if source_order_val:
440 source_order = source_order_val.split(",")
441 else:
442 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
443
444 # Check that all sources in ro.product.property_source_order are valid
445 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
446 for x in source_order]):
447 raise ExternalError(
448 "Invalid ro.product.property_source_order '{}'".format(source_order))
449
450 for source in source_order:
451 source_prop = prop.replace(
452 "ro.product", "ro.product.{}".format(source), 1)
453 prop_val = self.info_dict.get(
454 "{}.build.prop".format(source), {}).get(source_prop)
455 if prop_val:
456 return prop_val
457
458 raise ExternalError("couldn't resolve {}".format(prop))
459
460 def GetVendorBuildProp(self, prop):
461 """Returns the inquired vendor build property."""
462 try:
463 return self.info_dict.get("vendor.build.prop", {})[prop]
464 except KeyError:
465 raise ExternalError(
466 "couldn't find %s in vendor.build.prop" % (prop,))
467
468 def GetOemProperty(self, key):
469 if self.oem_props is not None and key in self.oem_props:
470 return self.oem_dicts[0][key]
471 return self.GetBuildProp(key)
472
473 def CalculateFingerprint(self):
474 if self.oem_props is None:
475 try:
476 return self.GetBuildProp("ro.build.fingerprint")
477 except ExternalError:
478 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
479 self.GetBuildProp("ro.product.brand"),
480 self.GetBuildProp("ro.product.name"),
481 self.GetBuildProp("ro.product.device"),
482 self.GetBuildProp("ro.build.version.release"),
483 self.GetBuildProp("ro.build.id"),
484 self.GetBuildProp("ro.build.version.incremental"),
485 self.GetBuildProp("ro.build.type"),
486 self.GetBuildProp("ro.build.tags"))
487 return "%s/%s/%s:%s" % (
488 self.GetOemProperty("ro.product.brand"),
489 self.GetOemProperty("ro.product.name"),
490 self.GetOemProperty("ro.product.device"),
491 self.GetBuildProp("ro.build.thumbprint"))
492
493 def WriteMountOemScript(self, script):
494 assert self.oem_props is not None
495 recovery_mount_options = self.info_dict.get("recovery_mount_options")
496 script.Mount("/oem", recovery_mount_options)
497
498 def WriteDeviceAssertions(self, script, oem_no_mount):
499 # Read the property directly if not using OEM properties.
500 if not self.oem_props:
501 script.AssertDevice(self.device)
502 return
503
504 # Otherwise assert OEM properties.
505 if not self.oem_dicts:
506 raise ExternalError(
507 "No OEM file provided to answer expected assertions")
508
509 for prop in self.oem_props.split():
510 values = []
511 for oem_dict in self.oem_dicts:
512 if prop in oem_dict:
513 values.append(oem_dict[prop])
514 if not values:
515 raise ExternalError(
516 "The OEM file is missing the property %s" % (prop,))
517 script.AssertOemProperty(prop, values, oem_no_mount)
518
519
Tao Bao410ad8b2018-08-24 12:08:38 -0700520def LoadInfoDict(input_file, repacking=False):
521 """Loads the key/value pairs from the given input target_files.
522
523 It reads `META/misc_info.txt` file in the target_files input, does sanity
524 checks and returns the parsed key/value pairs for to the given build. It's
525 usually called early when working on input target_files files, e.g. when
526 generating OTAs, or signing builds. Note that the function may be called
527 against an old target_files file (i.e. from past dessert releases). So the
528 property parsing needs to be backward compatible.
529
530 In a `META/misc_info.txt`, a few properties are stored as links to the files
531 in the PRODUCT_OUT directory. It works fine with the build system. However,
532 they are no longer available when (re)generating images from target_files zip.
533 When `repacking` is True, redirect these properties to the actual files in the
534 unzipped directory.
535
536 Args:
537 input_file: The input target_files file, which could be an open
538 zipfile.ZipFile instance, or a str for the dir that contains the files
539 unzipped from a target_files file.
540 repacking: Whether it's trying repack an target_files file after loading the
541 info dict (default: False). If so, it will rewrite a few loaded
542 properties (e.g. selinux_fc, root_dir) to point to the actual files in
543 target_files file. When doing repacking, `input_file` must be a dir.
544
545 Returns:
546 A dict that contains the parsed key/value pairs.
547
548 Raises:
549 AssertionError: On invalid input arguments.
550 ValueError: On malformed input values.
551 """
552 if repacking:
553 assert isinstance(input_file, str), \
554 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700555
Doug Zongkerc9253822014-02-04 12:17:58 -0800556 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700557 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800558 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800559 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700560 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800561 try:
562 with open(path) as f:
563 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700564 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800565 if e.errno == errno.ENOENT:
566 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800567
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700568 try:
Michael Runge6e836112014-04-15 17:40:21 -0700569 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700570 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700571 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700572
Tao Bao410ad8b2018-08-24 12:08:38 -0700573 if "recovery_api_version" not in d:
574 raise ValueError("Failed to find 'recovery_api_version'")
575 if "fstab_version" not in d:
576 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800577
Tao Bao410ad8b2018-08-24 12:08:38 -0700578 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700579 # "selinux_fc" properties should point to the file_contexts files
580 # (file_contexts.bin) under META/.
581 for key in d:
582 if key.endswith("selinux_fc"):
583 fc_basename = os.path.basename(d[key])
584 fc_config = os.path.join(input_file, "META", fc_basename)
585 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700586
Daniel Norman72c626f2019-05-13 15:58:14 -0700587 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700588
Tom Cherryd14b8952018-08-09 14:26:00 -0700589 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700590 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700591 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700592 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700593
David Anderson0ec64ac2019-12-06 12:21:18 -0800594 # Redirect {partition}_base_fs_file for each of the named partitions.
595 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
596 key_name = part_name + "_base_fs_file"
597 if key_name not in d:
598 continue
599 basename = os.path.basename(d[key_name])
600 base_fs_file = os.path.join(input_file, "META", basename)
601 if os.path.exists(base_fs_file):
602 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700603 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700604 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800605 "Failed to find %s base fs file: %s", part_name, base_fs_file)
606 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700607
Doug Zongker37974732010-09-16 17:44:38 -0700608 def makeint(key):
609 if key in d:
610 d[key] = int(d[key], 0)
611
612 makeint("recovery_api_version")
613 makeint("blocksize")
614 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700615 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700616 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700617 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700618 makeint("recovery_size")
619 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800620 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700621
Tao Bao765668f2019-10-04 22:03:00 -0700622 # Load recovery fstab if applicable.
623 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800624
Tianjie Xu861f4132018-09-12 11:49:33 -0700625 # Tries to load the build props for all partitions with care_map, including
626 # system and vendor.
627 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800628 partition_prop = "{}.build.prop".format(partition)
629 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700630 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800631 # Some partition might use /<partition>/etc/build.prop as the new path.
632 # TODO: try new path first when majority of them switch to the new path.
633 if not d[partition_prop]:
634 d[partition_prop] = LoadBuildProp(
635 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700636 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800637
Tao Bao3ed35d32019-10-07 20:48:48 -0700638 # Set up the salt (based on fingerprint) that will be used when adding AVB
639 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800640 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700641 build_info = BuildInfo(d)
642 d["avb_salt"] = sha256(build_info.fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800643
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700644 return d
645
Tao Baod1de6f32017-03-01 16:38:48 -0800646
Tao Baobcd1d162017-08-26 13:10:26 -0700647def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700648 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700649 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700650 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700651 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700652 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700653 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700654
Tao Baod1de6f32017-03-01 16:38:48 -0800655
Daniel Norman4cc9df62019-07-18 10:11:07 -0700656def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900657 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700658 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900659
Daniel Norman4cc9df62019-07-18 10:11:07 -0700660
661def LoadDictionaryFromFile(file_path):
662 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900663 return LoadDictionaryFromLines(lines)
664
665
Michael Runge6e836112014-04-15 17:40:21 -0700666def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700667 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700668 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700669 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700670 if not line or line.startswith("#"):
671 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700672 if "=" in line:
673 name, value = line.split("=", 1)
674 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700675 return d
676
Tao Baod1de6f32017-03-01 16:38:48 -0800677
Tianjie Xucfa86222016-03-07 16:31:19 -0800678def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
679 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700680 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800681 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700682 self.mount_point = mount_point
683 self.fs_type = fs_type
684 self.device = device
685 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700686 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700687
688 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800689 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700690 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700691 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700692 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700693
Tao Baod1de6f32017-03-01 16:38:48 -0800694 assert fstab_version == 2
695
696 d = {}
697 for line in data.split("\n"):
698 line = line.strip()
699 if not line or line.startswith("#"):
700 continue
701
702 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
703 pieces = line.split()
704 if len(pieces) != 5:
705 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
706
707 # Ignore entries that are managed by vold.
708 options = pieces[4]
709 if "voldmanaged=" in options:
710 continue
711
712 # It's a good line, parse it.
713 length = 0
714 options = options.split(",")
715 for i in options:
716 if i.startswith("length="):
717 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800718 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800719 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700720 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800721
Tao Baod1de6f32017-03-01 16:38:48 -0800722 mount_flags = pieces[3]
723 # Honor the SELinux context if present.
724 context = None
725 for i in mount_flags.split(","):
726 if i.startswith("context="):
727 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800728
Tao Baod1de6f32017-03-01 16:38:48 -0800729 mount_point = pieces[1]
730 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
731 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800732
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700733 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700734 # system. Other areas assume system is always at "/system" so point /system
735 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700736 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800737 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700738 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700739 return d
740
741
Tao Bao765668f2019-10-04 22:03:00 -0700742def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
743 """Finds the path to recovery fstab and loads its contents."""
744 # recovery fstab is only meaningful when installing an update via recovery
745 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
746 if info_dict.get('ab_update') == 'true':
747 return None
748
749 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
750 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
751 # cases, since it may load the info_dict from an old build (e.g. when
752 # generating incremental OTAs from that build).
753 system_root_image = info_dict.get('system_root_image') == 'true'
754 if info_dict.get('no_recovery') != 'true':
755 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
756 if isinstance(input_file, zipfile.ZipFile):
757 if recovery_fstab_path not in input_file.namelist():
758 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
759 else:
760 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
761 if not os.path.exists(path):
762 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
763 return LoadRecoveryFSTab(
764 read_helper, info_dict['fstab_version'], recovery_fstab_path,
765 system_root_image)
766
767 if info_dict.get('recovery_as_boot') == 'true':
768 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
769 if isinstance(input_file, zipfile.ZipFile):
770 if recovery_fstab_path not in input_file.namelist():
771 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
772 else:
773 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
774 if not os.path.exists(path):
775 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
776 return LoadRecoveryFSTab(
777 read_helper, info_dict['fstab_version'], recovery_fstab_path,
778 system_root_image)
779
780 return None
781
782
Doug Zongker37974732010-09-16 17:44:38 -0700783def DumpInfoDict(d):
784 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700785 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700786
Dan Albert8b72aef2015-03-23 19:13:21 -0700787
Daniel Norman55417142019-11-25 16:04:36 -0800788def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700789 """Merges dynamic partition info variables.
790
791 Args:
792 framework_dict: The dictionary of dynamic partition info variables from the
793 partial framework target files.
794 vendor_dict: The dictionary of dynamic partition info variables from the
795 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700796
797 Returns:
798 The merged dynamic partition info dictionary.
799 """
800 merged_dict = {}
801 # Partition groups and group sizes are defined by the vendor dict because
802 # these values may vary for each board that uses a shared system image.
803 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800804 framework_dynamic_partition_list = framework_dict.get(
805 "dynamic_partition_list", "")
806 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
807 merged_dict["dynamic_partition_list"] = ("%s %s" % (
808 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700809 for partition_group in merged_dict["super_partition_groups"].split(" "):
810 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800811 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700812 if key not in vendor_dict:
813 raise ValueError("Vendor dict does not contain required key %s." % key)
814 merged_dict[key] = vendor_dict[key]
815
816 # Set the partition group's partition list using a concatenation of the
817 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800818 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700819 merged_dict[key] = (
820 "%s %s" %
821 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
822 return merged_dict
823
824
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800825def AppendAVBSigningArgs(cmd, partition):
826 """Append signing arguments for avbtool."""
827 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
828 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700829 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
830 new_key_path = os.path.join(OPTIONS.search_path, key_path)
831 if os.path.exists(new_key_path):
832 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800833 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
834 if key_path and algorithm:
835 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700836 avb_salt = OPTIONS.info_dict.get("avb_salt")
837 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700838 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700839 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800840
841
Tao Bao765668f2019-10-04 22:03:00 -0700842def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700843 """Returns the VBMeta arguments for partition.
844
845 It sets up the VBMeta argument by including the partition descriptor from the
846 given 'image', or by configuring the partition as a chained partition.
847
848 Args:
849 partition: The name of the partition (e.g. "system").
850 image: The path to the partition image.
851 info_dict: A dict returned by common.LoadInfoDict(). Will use
852 OPTIONS.info_dict if None has been given.
853
854 Returns:
855 A list of VBMeta arguments.
856 """
857 if info_dict is None:
858 info_dict = OPTIONS.info_dict
859
860 # Check if chain partition is used.
861 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +0800862 if not key_path:
863 return ["--include_descriptors_from_image", image]
864
865 # For a non-A/B device, we don't chain /recovery nor include its descriptor
866 # into vbmeta.img. The recovery image will be configured on an independent
867 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
868 # See details at
869 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -0700870 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +0800871 return []
872
873 # Otherwise chain the partition into vbmeta.
874 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
875 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -0700876
877
Tao Bao02a08592018-07-22 12:40:45 -0700878def GetAvbChainedPartitionArg(partition, info_dict, key=None):
879 """Constructs and returns the arg to build or verify a chained partition.
880
881 Args:
882 partition: The partition name.
883 info_dict: The info dict to look up the key info and rollback index
884 location.
885 key: The key to be used for building or verifying the partition. Defaults to
886 the key listed in info_dict.
887
888 Returns:
889 A string of form "partition:rollback_index_location:key" that can be used to
890 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700891 """
892 if key is None:
893 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700894 if key and not os.path.exists(key) and OPTIONS.search_path:
895 new_key_path = os.path.join(OPTIONS.search_path, key)
896 if os.path.exists(new_key_path):
897 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700898 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700899 rollback_index_location = info_dict[
900 "avb_" + partition + "_rollback_index_location"]
901 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
902
903
Daniel Norman276f0622019-07-26 14:13:51 -0700904def BuildVBMeta(image_path, partitions, name, needed_partitions):
905 """Creates a VBMeta image.
906
907 It generates the requested VBMeta image. The requested image could be for
908 top-level or chained VBMeta image, which is determined based on the name.
909
910 Args:
911 image_path: The output path for the new VBMeta image.
912 partitions: A dict that's keyed by partition names with image paths as
913 values. Only valid partition names are accepted, as listed in
914 common.AVB_PARTITIONS.
915 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
916 needed_partitions: Partitions whose descriptors should be included into the
917 generated VBMeta image.
918
919 Raises:
920 AssertionError: On invalid input args.
921 """
922 avbtool = OPTIONS.info_dict["avb_avbtool"]
923 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
924 AppendAVBSigningArgs(cmd, name)
925
926 for partition, path in partitions.items():
927 if partition not in needed_partitions:
928 continue
929 assert (partition in AVB_PARTITIONS or
930 partition in AVB_VBMETA_PARTITIONS), \
931 'Unknown partition: {}'.format(partition)
932 assert os.path.exists(path), \
933 'Failed to find {} for {}'.format(path, partition)
934 cmd.extend(GetAvbPartitionArg(partition, path))
935
936 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
937 if args and args.strip():
938 split_args = shlex.split(args)
939 for index, arg in enumerate(split_args[:-1]):
940 # Sanity check that the image file exists. Some images might be defined
941 # as a path relative to source tree, which may not be available at the
942 # same location when running this script (we have the input target_files
943 # zip only). For such cases, we additionally scan other locations (e.g.
944 # IMAGES/, RADIO/, etc) before bailing out.
945 if arg == '--include_descriptors_from_image':
946 image_path = split_args[index + 1]
947 if os.path.exists(image_path):
948 continue
949 found = False
950 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
951 alt_path = os.path.join(
952 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
953 if os.path.exists(alt_path):
954 split_args[index + 1] = alt_path
955 found = True
956 break
957 assert found, 'Failed to find {}'.format(image_path)
958 cmd.extend(split_args)
959
960 RunAndCheckOutput(cmd)
961
962
Steve Mucklee1b10862019-07-10 10:49:37 -0700963def _MakeRamdisk(sourcedir, fs_config_file=None):
964 ramdisk_img = tempfile.NamedTemporaryFile()
965
966 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
967 cmd = ["mkbootfs", "-f", fs_config_file,
968 os.path.join(sourcedir, "RAMDISK")]
969 else:
970 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
971 p1 = Run(cmd, stdout=subprocess.PIPE)
972 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
973
974 p2.wait()
975 p1.wait()
976 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
977 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
978
979 return ramdisk_img
980
981
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700982def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800983 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700984 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700985
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700986 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800987 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
988 we are building a two-step special image (i.e. building a recovery image to
989 be loaded into /boot in two-step OTAs).
990
991 Return the image data, or None if sourcedir does not appear to contains files
992 for building the requested image.
993 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700994
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700995 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
996 return None
997
998 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700999 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001000
Doug Zongkerd5131602012-08-02 14:46:42 -07001001 if info_dict is None:
1002 info_dict = OPTIONS.info_dict
1003
Doug Zongkereef39442009-04-02 12:14:19 -07001004 img = tempfile.NamedTemporaryFile()
1005
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001006 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001007 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001008
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001009 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1010 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1011
1012 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -07001013
Benoit Fradina45a8682014-07-14 21:00:43 +02001014 fn = os.path.join(sourcedir, "second")
1015 if os.access(fn, os.F_OK):
1016 cmd.append("--second")
1017 cmd.append(fn)
1018
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001019 fn = os.path.join(sourcedir, "dtb")
1020 if os.access(fn, os.F_OK):
1021 cmd.append("--dtb")
1022 cmd.append(fn)
1023
Doug Zongker171f1cd2009-06-15 22:36:37 -07001024 fn = os.path.join(sourcedir, "cmdline")
1025 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001026 cmd.append("--cmdline")
1027 cmd.append(open(fn).read().rstrip("\n"))
1028
1029 fn = os.path.join(sourcedir, "base")
1030 if os.access(fn, os.F_OK):
1031 cmd.append("--base")
1032 cmd.append(open(fn).read().rstrip("\n"))
1033
Ying Wang4de6b5b2010-08-25 14:29:34 -07001034 fn = os.path.join(sourcedir, "pagesize")
1035 if os.access(fn, os.F_OK):
1036 cmd.append("--pagesize")
1037 cmd.append(open(fn).read().rstrip("\n"))
1038
Tao Bao76def242017-11-21 09:25:31 -08001039 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001040 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001041 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001042
Tao Bao76def242017-11-21 09:25:31 -08001043 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001044 if args and args.strip():
1045 cmd.extend(shlex.split(args))
1046
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001047 if has_ramdisk:
1048 cmd.extend(["--ramdisk", ramdisk_img.name])
1049
Tao Baod95e9fd2015-03-29 23:07:41 -07001050 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001051 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001052 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001053 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001054 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001055 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001056
Tao Baobf70c3182017-07-11 17:27:55 -07001057 # "boot" or "recovery", without extension.
1058 partition_name = os.path.basename(sourcedir).lower()
1059
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001060 if partition_name == "recovery":
1061 if info_dict.get("include_recovery_dtbo") == "true":
1062 fn = os.path.join(sourcedir, "recovery_dtbo")
1063 cmd.extend(["--recovery_dtbo", fn])
1064 if info_dict.get("include_recovery_acpio") == "true":
1065 fn = os.path.join(sourcedir, "recovery_acpio")
1066 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001067
Tao Bao986ee862018-10-04 15:46:16 -07001068 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001069
Tao Bao76def242017-11-21 09:25:31 -08001070 if (info_dict.get("boot_signer") == "true" and
1071 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001072 # Hard-code the path as "/boot" for two-step special recovery image (which
1073 # will be loaded into /boot during the two-step OTA).
1074 if two_step_image:
1075 path = "/boot"
1076 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001077 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001078 cmd = [OPTIONS.boot_signer_path]
1079 cmd.extend(OPTIONS.boot_signer_args)
1080 cmd.extend([path, img.name,
1081 info_dict["verity_key"] + ".pk8",
1082 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001083 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001084
Tao Baod95e9fd2015-03-29 23:07:41 -07001085 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001086 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001087 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001088 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001089 # We have switched from the prebuilt futility binary to using the tool
1090 # (futility-host) built from the source. Override the setting in the old
1091 # TF.zip.
1092 futility = info_dict["futility"]
1093 if futility.startswith("prebuilts/"):
1094 futility = "futility-host"
1095 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001096 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001097 info_dict["vboot_key"] + ".vbprivk",
1098 info_dict["vboot_subkey"] + ".vbprivk",
1099 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001100 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001101 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001102
Tao Baof3282b42015-04-01 11:21:55 -07001103 # Clean up the temp files.
1104 img_unsigned.close()
1105 img_keyblock.close()
1106
David Zeuthen8fecb282017-12-01 16:24:01 -05001107 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001108 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001109 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001110 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001111 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001112 "--partition_size", str(part_size), "--partition_name",
1113 partition_name]
1114 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001115 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001116 if args and args.strip():
1117 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001118 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001119
1120 img.seek(os.SEEK_SET, 0)
1121 data = img.read()
1122
1123 if has_ramdisk:
1124 ramdisk_img.close()
1125 img.close()
1126
1127 return data
1128
1129
Doug Zongkerd5131602012-08-02 14:46:42 -07001130def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001131 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001132 """Return a File object with the desired bootable image.
1133
1134 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1135 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1136 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001137
Doug Zongker55d93282011-01-25 17:03:34 -08001138 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1139 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001140 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001141 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001142
1143 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1144 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001145 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001146 return File.FromLocalFile(name, prebuilt_path)
1147
Tao Bao32fcdab2018-10-12 10:30:39 -07001148 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001149
1150 if info_dict is None:
1151 info_dict = OPTIONS.info_dict
1152
1153 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001154 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1155 # for recovery.
1156 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1157 prebuilt_name != "boot.img" or
1158 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001159
Doug Zongker6f1d0312014-08-22 08:07:12 -07001160 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001161 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
1162 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001163 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001164 if data:
1165 return File(name, data)
1166 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001167
Doug Zongkereef39442009-04-02 12:14:19 -07001168
Steve Mucklee1b10862019-07-10 10:49:37 -07001169def _BuildVendorBootImage(sourcedir, info_dict=None):
1170 """Build a vendor boot image from the specified sourcedir.
1171
1172 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1173 turn them into a vendor boot image.
1174
1175 Return the image data, or None if sourcedir does not appear to contains files
1176 for building the requested image.
1177 """
1178
1179 if info_dict is None:
1180 info_dict = OPTIONS.info_dict
1181
1182 img = tempfile.NamedTemporaryFile()
1183
1184 ramdisk_img = _MakeRamdisk(sourcedir)
1185
1186 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1187 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1188
1189 cmd = [mkbootimg]
1190
1191 fn = os.path.join(sourcedir, "dtb")
1192 if os.access(fn, os.F_OK):
1193 cmd.append("--dtb")
1194 cmd.append(fn)
1195
1196 fn = os.path.join(sourcedir, "vendor_cmdline")
1197 if os.access(fn, os.F_OK):
1198 cmd.append("--vendor_cmdline")
1199 cmd.append(open(fn).read().rstrip("\n"))
1200
1201 fn = os.path.join(sourcedir, "base")
1202 if os.access(fn, os.F_OK):
1203 cmd.append("--base")
1204 cmd.append(open(fn).read().rstrip("\n"))
1205
1206 fn = os.path.join(sourcedir, "pagesize")
1207 if os.access(fn, os.F_OK):
1208 cmd.append("--pagesize")
1209 cmd.append(open(fn).read().rstrip("\n"))
1210
1211 args = info_dict.get("mkbootimg_args")
1212 if args and args.strip():
1213 cmd.extend(shlex.split(args))
1214
1215 args = info_dict.get("mkbootimg_version_args")
1216 if args and args.strip():
1217 cmd.extend(shlex.split(args))
1218
1219 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1220 cmd.extend(["--vendor_boot", img.name])
1221
1222 RunAndCheckOutput(cmd)
1223
1224 # AVB: if enabled, calculate and add hash.
1225 if info_dict.get("avb_enable") == "true":
1226 avbtool = info_dict["avb_avbtool"]
1227 part_size = info_dict["vendor_boot_size"]
1228 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001229 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001230 AppendAVBSigningArgs(cmd, "vendor_boot")
1231 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1232 if args and args.strip():
1233 cmd.extend(shlex.split(args))
1234 RunAndCheckOutput(cmd)
1235
1236 img.seek(os.SEEK_SET, 0)
1237 data = img.read()
1238
1239 ramdisk_img.close()
1240 img.close()
1241
1242 return data
1243
1244
1245def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1246 info_dict=None):
1247 """Return a File object with the desired vendor boot image.
1248
1249 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1250 the source files in 'unpack_dir'/'tree_subdir'."""
1251
1252 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1253 if os.path.exists(prebuilt_path):
1254 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1255 return File.FromLocalFile(name, prebuilt_path)
1256
1257 logger.info("building image from target_files %s...", tree_subdir)
1258
1259 if info_dict is None:
1260 info_dict = OPTIONS.info_dict
1261
1262 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1263 if data:
1264 return File(name, data)
1265 return None
1266
1267
Narayan Kamatha07bf042017-08-14 14:49:21 +01001268def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001269 """Gunzips the given gzip compressed file to a given output file."""
1270 with gzip.open(in_filename, "rb") as in_file, \
1271 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001272 shutil.copyfileobj(in_file, out_file)
1273
1274
Tao Bao0ff15de2019-03-20 11:26:06 -07001275def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001276 """Unzips the archive to the given directory.
1277
1278 Args:
1279 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001280 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001281 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1282 archvie. Non-matching patterns will be filtered out. If there's no match
1283 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001284 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001285 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001286 if patterns is not None:
1287 # Filter out non-matching patterns. unzip will complain otherwise.
1288 with zipfile.ZipFile(filename) as input_zip:
1289 names = input_zip.namelist()
1290 filtered = [
1291 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1292
1293 # There isn't any matching files. Don't unzip anything.
1294 if not filtered:
1295 return
1296 cmd.extend(filtered)
1297
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001298 RunAndCheckOutput(cmd)
1299
1300
Doug Zongker75f17362009-12-08 13:46:44 -08001301def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001302 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001303
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001304 Args:
1305 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1306 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1307
1308 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1309 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001310
Tao Bao1c830bf2017-12-25 10:43:47 -08001311 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001312 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001313 """
Doug Zongkereef39442009-04-02 12:14:19 -07001314
Tao Bao1c830bf2017-12-25 10:43:47 -08001315 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001316 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1317 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001318 UnzipToDir(m.group(1), tmp, pattern)
1319 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001320 filename = m.group(1)
1321 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001322 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001323
Tao Baodba59ee2018-01-09 13:21:02 -08001324 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001325
1326
Yifan Hong8a66a712019-04-04 15:37:57 -07001327def GetUserImage(which, tmpdir, input_zip,
1328 info_dict=None,
1329 allow_shared_blocks=None,
1330 hashtree_info_generator=None,
1331 reset_file_map=False):
1332 """Returns an Image object suitable for passing to BlockImageDiff.
1333
1334 This function loads the specified image from the given path. If the specified
1335 image is sparse, it also performs additional processing for OTA purpose. For
1336 example, it always adds block 0 to clobbered blocks list. It also detects
1337 files that cannot be reconstructed from the block list, for whom we should
1338 avoid applying imgdiff.
1339
1340 Args:
1341 which: The partition name.
1342 tmpdir: The directory that contains the prebuilt image and block map file.
1343 input_zip: The target-files ZIP archive.
1344 info_dict: The dict to be looked up for relevant info.
1345 allow_shared_blocks: If image is sparse, whether having shared blocks is
1346 allowed. If none, it is looked up from info_dict.
1347 hashtree_info_generator: If present and image is sparse, generates the
1348 hashtree_info for this sparse image.
1349 reset_file_map: If true and image is sparse, reset file map before returning
1350 the image.
1351 Returns:
1352 A Image object. If it is a sparse image and reset_file_map is False, the
1353 image will have file_map info loaded.
1354 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001355 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001356 info_dict = LoadInfoDict(input_zip)
1357
1358 is_sparse = info_dict.get("extfs_sparse_flag")
1359
1360 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1361 # shared blocks (i.e. some blocks will show up in multiple files' block
1362 # list). We can only allocate such shared blocks to the first "owner", and
1363 # disable imgdiff for all later occurrences.
1364 if allow_shared_blocks is None:
1365 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1366
1367 if is_sparse:
1368 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1369 hashtree_info_generator)
1370 if reset_file_map:
1371 img.ResetFileMap()
1372 return img
1373 else:
1374 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1375
1376
1377def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1378 """Returns a Image object suitable for passing to BlockImageDiff.
1379
1380 This function loads the specified non-sparse image from the given path.
1381
1382 Args:
1383 which: The partition name.
1384 tmpdir: The directory that contains the prebuilt image and block map file.
1385 Returns:
1386 A Image object.
1387 """
1388 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1389 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1390
1391 # The image and map files must have been created prior to calling
1392 # ota_from_target_files.py (since LMP).
1393 assert os.path.exists(path) and os.path.exists(mappath)
1394
Tianjie Xu41976c72019-07-03 13:57:01 -07001395 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1396
Yifan Hong8a66a712019-04-04 15:37:57 -07001397
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001398def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1399 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001400 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1401
1402 This function loads the specified sparse image from the given path, and
1403 performs additional processing for OTA purpose. For example, it always adds
1404 block 0 to clobbered blocks list. It also detects files that cannot be
1405 reconstructed from the block list, for whom we should avoid applying imgdiff.
1406
1407 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001408 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001409 tmpdir: The directory that contains the prebuilt image and block map file.
1410 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001411 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001412 hashtree_info_generator: If present, generates the hashtree_info for this
1413 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001414 Returns:
1415 A SparseImage object, with file_map info loaded.
1416 """
Tao Baoc765cca2018-01-31 17:32:40 -08001417 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1418 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1419
1420 # The image and map files must have been created prior to calling
1421 # ota_from_target_files.py (since LMP).
1422 assert os.path.exists(path) and os.path.exists(mappath)
1423
1424 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1425 # it to clobbered_blocks so that it will be written to the target
1426 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1427 clobbered_blocks = "0"
1428
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001429 image = sparse_img.SparseImage(
1430 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1431 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001432
1433 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1434 # if they contain all zeros. We can't reconstruct such a file from its block
1435 # list. Tag such entries accordingly. (Bug: 65213616)
1436 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001437 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001438 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001439 continue
1440
Tom Cherryd14b8952018-08-09 14:26:00 -07001441 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1442 # filename listed in system.map may contain an additional leading slash
1443 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1444 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001445 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001446
Tom Cherryd14b8952018-08-09 14:26:00 -07001447 # Special handling another case, where files not under /system
1448 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001449 if which == 'system' and not arcname.startswith('SYSTEM'):
1450 arcname = 'ROOT/' + arcname
1451
1452 assert arcname in input_zip.namelist(), \
1453 "Failed to find the ZIP entry for {}".format(entry)
1454
Tao Baoc765cca2018-01-31 17:32:40 -08001455 info = input_zip.getinfo(arcname)
1456 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001457
1458 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001459 # image, check the original block list to determine its completeness. Note
1460 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001461 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001462 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001463
Tao Baoc765cca2018-01-31 17:32:40 -08001464 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1465 ranges.extra['incomplete'] = True
1466
1467 return image
1468
1469
Doug Zongkereef39442009-04-02 12:14:19 -07001470def GetKeyPasswords(keylist):
1471 """Given a list of keys, prompt the user to enter passwords for
1472 those which require them. Return a {key: password} dict. password
1473 will be None if the key has no password."""
1474
Doug Zongker8ce7c252009-05-22 13:34:54 -07001475 no_passwords = []
1476 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001477 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001478 devnull = open("/dev/null", "w+b")
1479 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001480 # We don't need a password for things that aren't really keys.
1481 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001482 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001483 continue
1484
T.R. Fullhart37e10522013-03-18 10:31:26 -07001485 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001486 "-inform", "DER", "-nocrypt"],
1487 stdin=devnull.fileno(),
1488 stdout=devnull.fileno(),
1489 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001490 p.communicate()
1491 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001492 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001493 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001494 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001495 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1496 "-inform", "DER", "-passin", "pass:"],
1497 stdin=devnull.fileno(),
1498 stdout=devnull.fileno(),
1499 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001500 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001501 if p.returncode == 0:
1502 # Encrypted key with empty string as password.
1503 key_passwords[k] = ''
1504 elif stderr.startswith('Error decrypting key'):
1505 # Definitely encrypted key.
1506 # It would have said "Error reading key" if it didn't parse correctly.
1507 need_passwords.append(k)
1508 else:
1509 # Potentially, a type of key that openssl doesn't understand.
1510 # We'll let the routines in signapk.jar handle it.
1511 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001512 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001513
T.R. Fullhart37e10522013-03-18 10:31:26 -07001514 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001515 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001516 return key_passwords
1517
1518
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001519def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001520 """Gets the minSdkVersion declared in the APK.
1521
changho.shin0f125362019-07-08 10:59:00 +09001522 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001523 This can be both a decimal number (API Level) or a codename.
1524
1525 Args:
1526 apk_name: The APK filename.
1527
1528 Returns:
1529 The parsed SDK version string.
1530
1531 Raises:
1532 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001533 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001534 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001535 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001536 stderr=subprocess.PIPE)
1537 stdoutdata, stderrdata = proc.communicate()
1538 if proc.returncode != 0:
1539 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001540 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001541 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001542
Tao Baof47bf0f2018-03-21 23:28:51 -07001543 for line in stdoutdata.split("\n"):
1544 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001545 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1546 if m:
1547 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001548 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001549
1550
1551def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001552 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001553
Tao Baof47bf0f2018-03-21 23:28:51 -07001554 If minSdkVersion is set to a codename, it is translated to a number using the
1555 provided map.
1556
1557 Args:
1558 apk_name: The APK filename.
1559
1560 Returns:
1561 The parsed SDK version number.
1562
1563 Raises:
1564 ExternalError: On failing to get the min SDK version number.
1565 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001566 version = GetMinSdkVersion(apk_name)
1567 try:
1568 return int(version)
1569 except ValueError:
1570 # Not a decimal number. Codename?
1571 if version in codename_to_api_level_map:
1572 return codename_to_api_level_map[version]
1573 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001574 raise ExternalError(
1575 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1576 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001577
1578
1579def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001580 codename_to_api_level_map=None, whole_file=False,
1581 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001582 """Sign the input_name zip/jar/apk, producing output_name. Use the
1583 given key and password (the latter may be None if the key does not
1584 have a password.
1585
Doug Zongker951495f2009-08-14 12:44:19 -07001586 If whole_file is true, use the "-w" option to SignApk to embed a
1587 signature that covers the whole file in the archive comment of the
1588 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001589
1590 min_api_level is the API Level (int) of the oldest platform this file may end
1591 up on. If not specified for an APK, the API Level is obtained by interpreting
1592 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1593
1594 codename_to_api_level_map is needed to translate the codename which may be
1595 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001596
1597 Caller may optionally specify extra args to be passed to SignApk, which
1598 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001599 """
Tao Bao76def242017-11-21 09:25:31 -08001600 if codename_to_api_level_map is None:
1601 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001602 if extra_signapk_args is None:
1603 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001604
Alex Klyubin9667b182015-12-10 13:38:50 -08001605 java_library_path = os.path.join(
1606 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1607
Tao Baoe95540e2016-11-08 12:08:53 -08001608 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1609 ["-Djava.library.path=" + java_library_path,
1610 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001611 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001612 if whole_file:
1613 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001614
1615 min_sdk_version = min_api_level
1616 if min_sdk_version is None:
1617 if not whole_file:
1618 min_sdk_version = GetMinSdkVersionInt(
1619 input_name, codename_to_api_level_map)
1620 if min_sdk_version is not None:
1621 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1622
T.R. Fullhart37e10522013-03-18 10:31:26 -07001623 cmd.extend([key + OPTIONS.public_key_suffix,
1624 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001625 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001626
Tao Bao73dd4f42018-10-04 16:25:33 -07001627 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001628 if password is not None:
1629 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001630 stdoutdata, _ = proc.communicate(password)
1631 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001632 raise ExternalError(
1633 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001634 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001635
Doug Zongkereef39442009-04-02 12:14:19 -07001636
Doug Zongker37974732010-09-16 17:44:38 -07001637def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001638 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001639
Tao Bao9dd909e2017-11-14 11:27:32 -08001640 For non-AVB images, raise exception if the data is too big. Print a warning
1641 if the data is nearing the maximum size.
1642
1643 For AVB images, the actual image size should be identical to the limit.
1644
1645 Args:
1646 data: A string that contains all the data for the partition.
1647 target: The partition name. The ".img" suffix is optional.
1648 info_dict: The dict to be looked up for relevant info.
1649 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001650 if target.endswith(".img"):
1651 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001652 mount_point = "/" + target
1653
Ying Wangf8824af2014-06-03 14:07:27 -07001654 fs_type = None
1655 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001656 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001657 if mount_point == "/userdata":
1658 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001659 p = info_dict["fstab"][mount_point]
1660 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001661 device = p.device
1662 if "/" in device:
1663 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001664 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001665 if not fs_type or not limit:
1666 return
Doug Zongkereef39442009-04-02 12:14:19 -07001667
Andrew Boie0f9aec82012-02-14 09:32:52 -08001668 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001669 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1670 # path.
1671 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1672 if size != limit:
1673 raise ExternalError(
1674 "Mismatching image size for %s: expected %d actual %d" % (
1675 target, limit, size))
1676 else:
1677 pct = float(size) * 100.0 / limit
1678 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1679 if pct >= 99.0:
1680 raise ExternalError(msg)
1681 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001682 logger.warning("\n WARNING: %s\n", msg)
1683 else:
1684 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001685
1686
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001687def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001688 """Parses the APK certs info from a given target-files zip.
1689
1690 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1691 tuple with the following elements: (1) a dictionary that maps packages to
1692 certs (based on the "certificate" and "private_key" attributes in the file;
1693 (2) a string representing the extension of compressed APKs in the target files
1694 (e.g ".gz", ".bro").
1695
1696 Args:
1697 tf_zip: The input target_files ZipFile (already open).
1698
1699 Returns:
1700 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1701 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1702 no compressed APKs.
1703 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001704 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001705 compressed_extension = None
1706
Tao Bao0f990332017-09-08 19:02:54 -07001707 # META/apkcerts.txt contains the info for _all_ the packages known at build
1708 # time. Filter out the ones that are not installed.
1709 installed_files = set()
1710 for name in tf_zip.namelist():
1711 basename = os.path.basename(name)
1712 if basename:
1713 installed_files.add(basename)
1714
Tao Baoda30cfa2017-12-01 16:19:46 -08001715 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001716 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001717 if not line:
1718 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001719 m = re.match(
1720 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1721 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1722 line)
1723 if not m:
1724 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001725
Tao Bao818ddf52018-01-05 11:17:34 -08001726 matches = m.groupdict()
1727 cert = matches["CERT"]
1728 privkey = matches["PRIVKEY"]
1729 name = matches["NAME"]
1730 this_compressed_extension = matches["COMPRESSED"]
1731
1732 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1733 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1734 if cert in SPECIAL_CERT_STRINGS and not privkey:
1735 certmap[name] = cert
1736 elif (cert.endswith(OPTIONS.public_key_suffix) and
1737 privkey.endswith(OPTIONS.private_key_suffix) and
1738 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1739 certmap[name] = cert[:-public_key_suffix_len]
1740 else:
1741 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1742
1743 if not this_compressed_extension:
1744 continue
1745
1746 # Only count the installed files.
1747 filename = name + '.' + this_compressed_extension
1748 if filename not in installed_files:
1749 continue
1750
1751 # Make sure that all the values in the compression map have the same
1752 # extension. We don't support multiple compression methods in the same
1753 # system image.
1754 if compressed_extension:
1755 if this_compressed_extension != compressed_extension:
1756 raise ValueError(
1757 "Multiple compressed extensions: {} vs {}".format(
1758 compressed_extension, this_compressed_extension))
1759 else:
1760 compressed_extension = this_compressed_extension
1761
1762 return (certmap,
1763 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001764
1765
Doug Zongkereef39442009-04-02 12:14:19 -07001766COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001767Global options
1768
1769 -p (--path) <dir>
1770 Prepend <dir>/bin to the list of places to search for binaries run by this
1771 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001772
Doug Zongker05d3dea2009-06-22 11:32:31 -07001773 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001774 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001775
Tao Bao30df8b42018-04-23 15:32:53 -07001776 -x (--extra) <key=value>
1777 Add a key/value pair to the 'extras' dict, which device-specific extension
1778 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001779
Doug Zongkereef39442009-04-02 12:14:19 -07001780 -v (--verbose)
1781 Show command lines being executed.
1782
1783 -h (--help)
1784 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001785
1786 --logfile <file>
1787 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001788"""
1789
1790def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001791 print(docstring.rstrip("\n"))
1792 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001793
1794
1795def ParseOptions(argv,
1796 docstring,
1797 extra_opts="", extra_long_opts=(),
1798 extra_option_handler=None):
1799 """Parse the options in argv and return any arguments that aren't
1800 flags. docstring is the calling module's docstring, to be displayed
1801 for errors and -h. extra_opts and extra_long_opts are for flags
1802 defined by the caller, which are processed by passing them to
1803 extra_option_handler."""
1804
1805 try:
1806 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001807 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001808 ["help", "verbose", "path=", "signapk_path=",
1809 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001810 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001811 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1812 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Yifan Hong30910932019-10-25 20:36:55 -07001813 "extra=", "logfile="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001814 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001815 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001816 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001817 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001818 sys.exit(2)
1819
Doug Zongkereef39442009-04-02 12:14:19 -07001820 for o, a in opts:
1821 if o in ("-h", "--help"):
1822 Usage(docstring)
1823 sys.exit()
1824 elif o in ("-v", "--verbose"):
1825 OPTIONS.verbose = True
1826 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001827 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001828 elif o in ("--signapk_path",):
1829 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001830 elif o in ("--signapk_shared_library_path",):
1831 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001832 elif o in ("--extra_signapk_args",):
1833 OPTIONS.extra_signapk_args = shlex.split(a)
1834 elif o in ("--java_path",):
1835 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001836 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001837 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001838 elif o in ("--public_key_suffix",):
1839 OPTIONS.public_key_suffix = a
1840 elif o in ("--private_key_suffix",):
1841 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001842 elif o in ("--boot_signer_path",):
1843 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001844 elif o in ("--boot_signer_args",):
1845 OPTIONS.boot_signer_args = shlex.split(a)
1846 elif o in ("--verity_signer_path",):
1847 OPTIONS.verity_signer_path = a
1848 elif o in ("--verity_signer_args",):
1849 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001850 elif o in ("-s", "--device_specific"):
1851 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001852 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001853 key, value = a.split("=", 1)
1854 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001855 elif o in ("--logfile",):
1856 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001857 else:
1858 if extra_option_handler is None or not extra_option_handler(o, a):
1859 assert False, "unknown option \"%s\"" % (o,)
1860
Doug Zongker85448772014-09-09 14:59:20 -07001861 if OPTIONS.search_path:
1862 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1863 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001864
1865 return args
1866
1867
Tao Bao4c851b12016-09-19 13:54:38 -07001868def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001869 """Make a temp file and add it to the list of things to be deleted
1870 when Cleanup() is called. Return the filename."""
1871 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1872 os.close(fd)
1873 OPTIONS.tempfiles.append(fn)
1874 return fn
1875
1876
Tao Bao1c830bf2017-12-25 10:43:47 -08001877def MakeTempDir(prefix='tmp', suffix=''):
1878 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1879
1880 Returns:
1881 The absolute pathname of the new directory.
1882 """
1883 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1884 OPTIONS.tempfiles.append(dir_name)
1885 return dir_name
1886
1887
Doug Zongkereef39442009-04-02 12:14:19 -07001888def Cleanup():
1889 for i in OPTIONS.tempfiles:
1890 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001891 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001892 else:
1893 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001894 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001895
1896
1897class PasswordManager(object):
1898 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001899 self.editor = os.getenv("EDITOR")
1900 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001901
1902 def GetPasswords(self, items):
1903 """Get passwords corresponding to each string in 'items',
1904 returning a dict. (The dict may have keys in addition to the
1905 values in 'items'.)
1906
1907 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1908 user edit that file to add more needed passwords. If no editor is
1909 available, or $ANDROID_PW_FILE isn't define, prompts the user
1910 interactively in the ordinary way.
1911 """
1912
1913 current = self.ReadFile()
1914
1915 first = True
1916 while True:
1917 missing = []
1918 for i in items:
1919 if i not in current or not current[i]:
1920 missing.append(i)
1921 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001922 if not missing:
1923 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001924
1925 for i in missing:
1926 current[i] = ""
1927
1928 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001929 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001930 if sys.version_info[0] >= 3:
1931 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001932 answer = raw_input("try to edit again? [y]> ").strip()
1933 if answer and answer[0] not in 'yY':
1934 raise RuntimeError("key passwords unavailable")
1935 first = False
1936
1937 current = self.UpdateAndReadFile(current)
1938
Dan Albert8b72aef2015-03-23 19:13:21 -07001939 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001940 """Prompt the user to enter a value (password) for each key in
1941 'current' whose value is fales. Returns a new dict with all the
1942 values.
1943 """
1944 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001945 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001946 if v:
1947 result[k] = v
1948 else:
1949 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001950 result[k] = getpass.getpass(
1951 "Enter password for %s key> " % k).strip()
1952 if result[k]:
1953 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001954 return result
1955
1956 def UpdateAndReadFile(self, current):
1957 if not self.editor or not self.pwfile:
1958 return self.PromptResult(current)
1959
1960 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001961 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001962 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1963 f.write("# (Additional spaces are harmless.)\n\n")
1964
1965 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001966 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001967 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001968 f.write("[[[ %s ]]] %s\n" % (v, k))
1969 if not v and first_line is None:
1970 # position cursor on first line with no password.
1971 first_line = i + 4
1972 f.close()
1973
Tao Bao986ee862018-10-04 15:46:16 -07001974 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001975
1976 return self.ReadFile()
1977
1978 def ReadFile(self):
1979 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001980 if self.pwfile is None:
1981 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001982 try:
1983 f = open(self.pwfile, "r")
1984 for line in f:
1985 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001986 if not line or line[0] == '#':
1987 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001988 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1989 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001990 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001991 else:
1992 result[m.group(2)] = m.group(1)
1993 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001994 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001995 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001996 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001997 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001998
1999
Dan Albert8e0178d2015-01-27 15:53:15 -08002000def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2001 compress_type=None):
2002 import datetime
2003
2004 # http://b/18015246
2005 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2006 # for files larger than 2GiB. We can work around this by adjusting their
2007 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2008 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2009 # it isn't clear to me exactly what circumstances cause this).
2010 # `zipfile.write()` must be used directly to work around this.
2011 #
2012 # This mess can be avoided if we port to python3.
2013 saved_zip64_limit = zipfile.ZIP64_LIMIT
2014 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2015
2016 if compress_type is None:
2017 compress_type = zip_file.compression
2018 if arcname is None:
2019 arcname = filename
2020
2021 saved_stat = os.stat(filename)
2022
2023 try:
2024 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2025 # file to be zipped and reset it when we're done.
2026 os.chmod(filename, perms)
2027
2028 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002029 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2030 # intentional. zip stores datetimes in local time without a time zone
2031 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2032 # in the zip archive.
2033 local_epoch = datetime.datetime.fromtimestamp(0)
2034 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002035 os.utime(filename, (timestamp, timestamp))
2036
2037 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2038 finally:
2039 os.chmod(filename, saved_stat.st_mode)
2040 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2041 zipfile.ZIP64_LIMIT = saved_zip64_limit
2042
2043
Tao Bao58c1b962015-05-20 09:32:18 -07002044def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002045 compress_type=None):
2046 """Wrap zipfile.writestr() function to work around the zip64 limit.
2047
2048 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2049 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2050 when calling crc32(bytes).
2051
2052 But it still works fine to write a shorter string into a large zip file.
2053 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2054 when we know the string won't be too long.
2055 """
2056
2057 saved_zip64_limit = zipfile.ZIP64_LIMIT
2058 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2059
2060 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2061 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002062 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002063 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002064 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002065 else:
Tao Baof3282b42015-04-01 11:21:55 -07002066 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002067 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2068 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2069 # such a case (since
2070 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2071 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2072 # permission bits. We follow the logic in Python 3 to get consistent
2073 # behavior between using the two versions.
2074 if not zinfo.external_attr:
2075 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002076
2077 # If compress_type is given, it overrides the value in zinfo.
2078 if compress_type is not None:
2079 zinfo.compress_type = compress_type
2080
Tao Bao58c1b962015-05-20 09:32:18 -07002081 # If perms is given, it has a priority.
2082 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002083 # If perms doesn't set the file type, mark it as a regular file.
2084 if perms & 0o770000 == 0:
2085 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002086 zinfo.external_attr = perms << 16
2087
Tao Baof3282b42015-04-01 11:21:55 -07002088 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002089 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2090
Dan Albert8b72aef2015-03-23 19:13:21 -07002091 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002092 zipfile.ZIP64_LIMIT = saved_zip64_limit
2093
2094
Tao Bao89d7ab22017-12-14 17:05:33 -08002095def ZipDelete(zip_filename, entries):
2096 """Deletes entries from a ZIP file.
2097
2098 Since deleting entries from a ZIP file is not supported, it shells out to
2099 'zip -d'.
2100
2101 Args:
2102 zip_filename: The name of the ZIP file.
2103 entries: The name of the entry, or the list of names to be deleted.
2104
2105 Raises:
2106 AssertionError: In case of non-zero return from 'zip'.
2107 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002108 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002109 entries = [entries]
2110 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002111 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002112
2113
Tao Baof3282b42015-04-01 11:21:55 -07002114def ZipClose(zip_file):
2115 # http://b/18015246
2116 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2117 # central directory.
2118 saved_zip64_limit = zipfile.ZIP64_LIMIT
2119 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2120
2121 zip_file.close()
2122
2123 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002124
2125
2126class DeviceSpecificParams(object):
2127 module = None
2128 def __init__(self, **kwargs):
2129 """Keyword arguments to the constructor become attributes of this
2130 object, which is passed to all functions in the device-specific
2131 module."""
Tao Bao38884282019-07-10 22:20:56 -07002132 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002133 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002134 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002135
2136 if self.module is None:
2137 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002138 if not path:
2139 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002140 try:
2141 if os.path.isdir(path):
2142 info = imp.find_module("releasetools", [path])
2143 else:
2144 d, f = os.path.split(path)
2145 b, x = os.path.splitext(f)
2146 if x == ".py":
2147 f = b
2148 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002149 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002150 self.module = imp.load_module("device_specific", *info)
2151 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002152 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002153
2154 def _DoCall(self, function_name, *args, **kwargs):
2155 """Call the named function in the device-specific module, passing
2156 the given args and kwargs. The first argument to the call will be
2157 the DeviceSpecific object itself. If there is no module, or the
2158 module does not define the function, return the value of the
2159 'default' kwarg (which itself defaults to None)."""
2160 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002161 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002162 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2163
2164 def FullOTA_Assertions(self):
2165 """Called after emitting the block of assertions at the top of a
2166 full OTA package. Implementations can add whatever additional
2167 assertions they like."""
2168 return self._DoCall("FullOTA_Assertions")
2169
Doug Zongkere5ff5902012-01-17 10:55:37 -08002170 def FullOTA_InstallBegin(self):
2171 """Called at the start of full OTA installation."""
2172 return self._DoCall("FullOTA_InstallBegin")
2173
Yifan Hong10c530d2018-12-27 17:34:18 -08002174 def FullOTA_GetBlockDifferences(self):
2175 """Called during full OTA installation and verification.
2176 Implementation should return a list of BlockDifference objects describing
2177 the update on each additional partitions.
2178 """
2179 return self._DoCall("FullOTA_GetBlockDifferences")
2180
Doug Zongker05d3dea2009-06-22 11:32:31 -07002181 def FullOTA_InstallEnd(self):
2182 """Called at the end of full OTA installation; typically this is
2183 used to install the image for the device's baseband processor."""
2184 return self._DoCall("FullOTA_InstallEnd")
2185
2186 def IncrementalOTA_Assertions(self):
2187 """Called after emitting the block of assertions at the top of an
2188 incremental OTA package. Implementations can add whatever
2189 additional assertions they like."""
2190 return self._DoCall("IncrementalOTA_Assertions")
2191
Doug Zongkere5ff5902012-01-17 10:55:37 -08002192 def IncrementalOTA_VerifyBegin(self):
2193 """Called at the start of the verification phase of incremental
2194 OTA installation; additional checks can be placed here to abort
2195 the script before any changes are made."""
2196 return self._DoCall("IncrementalOTA_VerifyBegin")
2197
Doug Zongker05d3dea2009-06-22 11:32:31 -07002198 def IncrementalOTA_VerifyEnd(self):
2199 """Called at the end of the verification phase of incremental OTA
2200 installation; additional checks can be placed here to abort the
2201 script before any changes are made."""
2202 return self._DoCall("IncrementalOTA_VerifyEnd")
2203
Doug Zongkere5ff5902012-01-17 10:55:37 -08002204 def IncrementalOTA_InstallBegin(self):
2205 """Called at the start of incremental OTA installation (after
2206 verification is complete)."""
2207 return self._DoCall("IncrementalOTA_InstallBegin")
2208
Yifan Hong10c530d2018-12-27 17:34:18 -08002209 def IncrementalOTA_GetBlockDifferences(self):
2210 """Called during incremental OTA installation and verification.
2211 Implementation should return a list of BlockDifference objects describing
2212 the update on each additional partitions.
2213 """
2214 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2215
Doug Zongker05d3dea2009-06-22 11:32:31 -07002216 def IncrementalOTA_InstallEnd(self):
2217 """Called at the end of incremental OTA installation; typically
2218 this is used to install the image for the device's baseband
2219 processor."""
2220 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002221
Tao Bao9bc6bb22015-11-09 16:58:28 -08002222 def VerifyOTA_Assertions(self):
2223 return self._DoCall("VerifyOTA_Assertions")
2224
Tao Bao76def242017-11-21 09:25:31 -08002225
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002226class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002227 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002228 self.name = name
2229 self.data = data
2230 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002231 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002232 self.sha1 = sha1(data).hexdigest()
2233
2234 @classmethod
2235 def FromLocalFile(cls, name, diskname):
2236 f = open(diskname, "rb")
2237 data = f.read()
2238 f.close()
2239 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002240
2241 def WriteToTemp(self):
2242 t = tempfile.NamedTemporaryFile()
2243 t.write(self.data)
2244 t.flush()
2245 return t
2246
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002247 def WriteToDir(self, d):
2248 with open(os.path.join(d, self.name), "wb") as fp:
2249 fp.write(self.data)
2250
Geremy Condra36bd3652014-02-06 19:45:10 -08002251 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002252 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002253
Tao Bao76def242017-11-21 09:25:31 -08002254
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002255DIFF_PROGRAM_BY_EXT = {
2256 ".gz" : "imgdiff",
2257 ".zip" : ["imgdiff", "-z"],
2258 ".jar" : ["imgdiff", "-z"],
2259 ".apk" : ["imgdiff", "-z"],
2260 ".img" : "imgdiff",
2261 }
2262
Tao Bao76def242017-11-21 09:25:31 -08002263
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002264class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002265 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002266 self.tf = tf
2267 self.sf = sf
2268 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002269 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002270
2271 def ComputePatch(self):
2272 """Compute the patch (as a string of data) needed to turn sf into
2273 tf. Returns the same tuple as GetPatch()."""
2274
2275 tf = self.tf
2276 sf = self.sf
2277
Doug Zongker24cd2802012-08-14 16:36:15 -07002278 if self.diff_program:
2279 diff_program = self.diff_program
2280 else:
2281 ext = os.path.splitext(tf.name)[1]
2282 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002283
2284 ttemp = tf.WriteToTemp()
2285 stemp = sf.WriteToTemp()
2286
2287 ext = os.path.splitext(tf.name)[1]
2288
2289 try:
2290 ptemp = tempfile.NamedTemporaryFile()
2291 if isinstance(diff_program, list):
2292 cmd = copy.copy(diff_program)
2293 else:
2294 cmd = [diff_program]
2295 cmd.append(stemp.name)
2296 cmd.append(ttemp.name)
2297 cmd.append(ptemp.name)
2298 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002299 err = []
2300 def run():
2301 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002302 if e:
2303 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002304 th = threading.Thread(target=run)
2305 th.start()
2306 th.join(timeout=300) # 5 mins
2307 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002308 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002309 p.terminate()
2310 th.join(5)
2311 if th.is_alive():
2312 p.kill()
2313 th.join()
2314
Tianjie Xua2a9f992018-01-05 15:15:54 -08002315 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002316 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002317 self.patch = None
2318 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002319 diff = ptemp.read()
2320 finally:
2321 ptemp.close()
2322 stemp.close()
2323 ttemp.close()
2324
2325 self.patch = diff
2326 return self.tf, self.sf, self.patch
2327
2328
2329 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002330 """Returns a tuple of (target_file, source_file, patch_data).
2331
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002332 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002333 computing the patch failed.
2334 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002335 return self.tf, self.sf, self.patch
2336
2337
2338def ComputeDifferences(diffs):
2339 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002340 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002341
2342 # Do the largest files first, to try and reduce the long-pole effect.
2343 by_size = [(i.tf.size, i) for i in diffs]
2344 by_size.sort(reverse=True)
2345 by_size = [i[1] for i in by_size]
2346
2347 lock = threading.Lock()
2348 diff_iter = iter(by_size) # accessed under lock
2349
2350 def worker():
2351 try:
2352 lock.acquire()
2353 for d in diff_iter:
2354 lock.release()
2355 start = time.time()
2356 d.ComputePatch()
2357 dur = time.time() - start
2358 lock.acquire()
2359
2360 tf, sf, patch = d.GetPatch()
2361 if sf.name == tf.name:
2362 name = tf.name
2363 else:
2364 name = "%s (%s)" % (tf.name, sf.name)
2365 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002366 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002367 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002368 logger.info(
2369 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2370 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002371 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002372 except Exception:
2373 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002374 raise
2375
2376 # start worker threads; wait for them all to finish.
2377 threads = [threading.Thread(target=worker)
2378 for i in range(OPTIONS.worker_threads)]
2379 for th in threads:
2380 th.start()
2381 while threads:
2382 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002383
2384
Dan Albert8b72aef2015-03-23 19:13:21 -07002385class BlockDifference(object):
2386 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002387 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002388 self.tgt = tgt
2389 self.src = src
2390 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002391 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002392 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002393
Tao Baodd2a5892015-03-12 12:32:37 -07002394 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002395 version = max(
2396 int(i) for i in
2397 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002398 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002399 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002400
Tianjie Xu41976c72019-07-03 13:57:01 -07002401 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2402 version=self.version,
2403 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002404 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002405 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002406 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002407 self.touched_src_ranges = b.touched_src_ranges
2408 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002409
Yifan Hong10c530d2018-12-27 17:34:18 -08002410 # On devices with dynamic partitions, for new partitions,
2411 # src is None but OPTIONS.source_info_dict is not.
2412 if OPTIONS.source_info_dict is None:
2413 is_dynamic_build = OPTIONS.info_dict.get(
2414 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002415 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002416 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002417 is_dynamic_build = OPTIONS.source_info_dict.get(
2418 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002419 is_dynamic_source = partition in shlex.split(
2420 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002421
Yifan Hongbb2658d2019-01-25 12:30:58 -08002422 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002423 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2424
Yifan Hongbb2658d2019-01-25 12:30:58 -08002425 # For dynamic partitions builds, check partition list in both source
2426 # and target build because new partitions may be added, and existing
2427 # partitions may be removed.
2428 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2429
Yifan Hong10c530d2018-12-27 17:34:18 -08002430 if is_dynamic:
2431 self.device = 'map_partition("%s")' % partition
2432 else:
2433 if OPTIONS.source_info_dict is None:
2434 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2435 else:
2436 _, device_path = GetTypeAndDevice("/" + partition,
2437 OPTIONS.source_info_dict)
2438 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002439
Tao Baod8d14be2016-02-04 14:26:02 -08002440 @property
2441 def required_cache(self):
2442 return self._required_cache
2443
Tao Bao76def242017-11-21 09:25:31 -08002444 def WriteScript(self, script, output_zip, progress=None,
2445 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002446 if not self.src:
2447 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002448 script.Print("Patching %s image unconditionally..." % (self.partition,))
2449 else:
2450 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002451
Dan Albert8b72aef2015-03-23 19:13:21 -07002452 if progress:
2453 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002454 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002455
2456 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002457 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002458
Tao Bao9bc6bb22015-11-09 16:58:28 -08002459 def WriteStrictVerifyScript(self, script):
2460 """Verify all the blocks in the care_map, including clobbered blocks.
2461
2462 This differs from the WriteVerifyScript() function: a) it prints different
2463 error messages; b) it doesn't allow half-way updated images to pass the
2464 verification."""
2465
2466 partition = self.partition
2467 script.Print("Verifying %s..." % (partition,))
2468 ranges = self.tgt.care_map
2469 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002470 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002471 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2472 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002473 self.device, ranges_str,
2474 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002475 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002476 script.AppendExtra("")
2477
Tao Baod522bdc2016-04-12 15:53:16 -07002478 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002479 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002480
2481 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002482 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002483 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002484
2485 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002486 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002487 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002488 ranges = self.touched_src_ranges
2489 expected_sha1 = self.touched_src_sha1
2490 else:
2491 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2492 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002493
2494 # No blocks to be checked, skipping.
2495 if not ranges:
2496 return
2497
Tao Bao5ece99d2015-05-12 11:42:31 -07002498 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002499 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002500 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002501 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2502 '"%s.patch.dat")) then' % (
2503 self.device, ranges_str, expected_sha1,
2504 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002505 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002506 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002507
Tianjie Xufc3422a2015-12-15 11:53:59 -08002508 if self.version >= 4:
2509
2510 # Bug: 21124327
2511 # When generating incrementals for the system and vendor partitions in
2512 # version 4 or newer, explicitly check the first block (which contains
2513 # the superblock) of the partition to see if it's what we expect. If
2514 # this check fails, give an explicit log message about the partition
2515 # having been remounted R/W (the most likely explanation).
2516 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002517 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002518
2519 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002520 if partition == "system":
2521 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2522 else:
2523 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002524 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002525 'ifelse (block_image_recover({device}, "{ranges}") && '
2526 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002527 'package_extract_file("{partition}.transfer.list"), '
2528 '"{partition}.new.dat", "{partition}.patch.dat"), '
2529 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002530 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002531 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002532 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002533
Tao Baodd2a5892015-03-12 12:32:37 -07002534 # Abort the OTA update. Note that the incremental OTA cannot be applied
2535 # even if it may match the checksum of the target partition.
2536 # a) If version < 3, operations like move and erase will make changes
2537 # unconditionally and damage the partition.
2538 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002539 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002540 if partition == "system":
2541 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2542 else:
2543 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2544 script.AppendExtra((
2545 'abort("E%d: %s partition has unexpected contents");\n'
2546 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002547
Yifan Hong10c530d2018-12-27 17:34:18 -08002548 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002549 partition = self.partition
2550 script.Print('Verifying the updated %s image...' % (partition,))
2551 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2552 ranges = self.tgt.care_map
2553 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002554 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002555 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002556 self.device, ranges_str,
2557 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002558
2559 # Bug: 20881595
2560 # Verify that extended blocks are really zeroed out.
2561 if self.tgt.extended:
2562 ranges_str = self.tgt.extended.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._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002567 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002568 if partition == "system":
2569 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2570 else:
2571 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002572 script.AppendExtra(
2573 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002574 ' abort("E%d: %s partition has unexpected non-zero contents after '
2575 'OTA update");\n'
2576 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002577 else:
2578 script.Print('Verified the updated %s image.' % (partition,))
2579
Tianjie Xu209db462016-05-24 17:34:52 -07002580 if partition == "system":
2581 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2582 else:
2583 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2584
Tao Bao5fcaaef2015-06-01 13:40:49 -07002585 script.AppendExtra(
2586 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002587 ' abort("E%d: %s partition has unexpected contents after OTA '
2588 'update");\n'
2589 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002590
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002591 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002592 ZipWrite(output_zip,
2593 '{}.transfer.list'.format(self.path),
2594 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002595
Tao Bao76def242017-11-21 09:25:31 -08002596 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2597 # its size. Quailty 9 almost triples the compression time but doesn't
2598 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002599 # zip | brotli(quality 6) | brotli(quality 9)
2600 # compressed_size: 942M | 869M (~8% reduced) | 854M
2601 # compression_time: 75s | 265s | 719s
2602 # decompression_time: 15s | 25s | 25s
2603
2604 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002605 brotli_cmd = ['brotli', '--quality=6',
2606 '--output={}.new.dat.br'.format(self.path),
2607 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002608 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002609 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002610
2611 new_data_name = '{}.new.dat.br'.format(self.partition)
2612 ZipWrite(output_zip,
2613 '{}.new.dat.br'.format(self.path),
2614 new_data_name,
2615 compress_type=zipfile.ZIP_STORED)
2616 else:
2617 new_data_name = '{}.new.dat'.format(self.partition)
2618 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2619
Dan Albert8e0178d2015-01-27 15:53:15 -08002620 ZipWrite(output_zip,
2621 '{}.patch.dat'.format(self.path),
2622 '{}.patch.dat'.format(self.partition),
2623 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002624
Tianjie Xu209db462016-05-24 17:34:52 -07002625 if self.partition == "system":
2626 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2627 else:
2628 code = ErrorCode.VENDOR_UPDATE_FAILURE
2629
Yifan Hong10c530d2018-12-27 17:34:18 -08002630 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002631 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002632 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002633 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002634 device=self.device, partition=self.partition,
2635 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002636 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002637
Dan Albert8b72aef2015-03-23 19:13:21 -07002638 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002639 data = source.ReadRangeSet(ranges)
2640 ctx = sha1()
2641
2642 for p in data:
2643 ctx.update(p)
2644
2645 return ctx.hexdigest()
2646
Tao Baoe9b61912015-07-09 17:37:49 -07002647 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2648 """Return the hash value for all zero blocks."""
2649 zero_block = '\x00' * 4096
2650 ctx = sha1()
2651 for _ in range(num_blocks):
2652 ctx.update(zero_block)
2653
2654 return ctx.hexdigest()
2655
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002656
Tianjie Xu41976c72019-07-03 13:57:01 -07002657# Expose these two classes to support vendor-specific scripts
2658DataImage = images.DataImage
2659EmptyImage = images.EmptyImage
2660
Tao Bao76def242017-11-21 09:25:31 -08002661
Doug Zongker96a57e72010-09-26 14:57:41 -07002662# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002663PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002664 "ext4": "EMMC",
2665 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002666 "f2fs": "EMMC",
2667 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002668}
Doug Zongker96a57e72010-09-26 14:57:41 -07002669
Tao Bao76def242017-11-21 09:25:31 -08002670
Doug Zongker96a57e72010-09-26 14:57:41 -07002671def GetTypeAndDevice(mount_point, info):
2672 fstab = info["fstab"]
2673 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002674 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2675 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002676 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002677 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002678
2679
2680def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002681 """Parses and converts a PEM-encoded certificate into DER-encoded.
2682
2683 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2684
2685 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002686 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002687 """
2688 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002689 save = False
2690 for line in data.split("\n"):
2691 if "--END CERTIFICATE--" in line:
2692 break
2693 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002694 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002695 if "--BEGIN CERTIFICATE--" in line:
2696 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002697 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002698 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002699
Tao Bao04e1f012018-02-04 12:13:35 -08002700
2701def ExtractPublicKey(cert):
2702 """Extracts the public key (PEM-encoded) from the given certificate file.
2703
2704 Args:
2705 cert: The certificate filename.
2706
2707 Returns:
2708 The public key string.
2709
2710 Raises:
2711 AssertionError: On non-zero return from 'openssl'.
2712 """
2713 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2714 # While openssl 1.1 writes the key into the given filename followed by '-out',
2715 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2716 # stdout instead.
2717 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2718 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2719 pubkey, stderrdata = proc.communicate()
2720 assert proc.returncode == 0, \
2721 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2722 return pubkey
2723
2724
Tao Bao1ac886e2019-06-26 11:58:22 -07002725def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002726 """Extracts the AVB public key from the given public or private key.
2727
2728 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002729 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002730 key: The input key file, which should be PEM-encoded public or private key.
2731
2732 Returns:
2733 The path to the extracted AVB public key file.
2734 """
2735 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2736 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002737 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002738 return output
2739
2740
Doug Zongker412c02f2014-02-13 10:58:24 -08002741def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2742 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002743 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002744
Tao Bao6d5d6232018-03-09 17:04:42 -08002745 Most of the space in the boot and recovery images is just the kernel, which is
2746 identical for the two, so the resulting patch should be efficient. Add it to
2747 the output zip, along with a shell script that is run from init.rc on first
2748 boot to actually do the patching and install the new recovery image.
2749
2750 Args:
2751 input_dir: The top-level input directory of the target-files.zip.
2752 output_sink: The callback function that writes the result.
2753 recovery_img: File object for the recovery image.
2754 boot_img: File objects for the boot image.
2755 info_dict: A dict returned by common.LoadInfoDict() on the input
2756 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002757 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002758 if info_dict is None:
2759 info_dict = OPTIONS.info_dict
2760
Tao Bao6d5d6232018-03-09 17:04:42 -08002761 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002762 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2763
2764 if board_uses_vendorimage:
2765 # In this case, the output sink is rooted at VENDOR
2766 recovery_img_path = "etc/recovery.img"
2767 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2768 sh_dir = "bin"
2769 else:
2770 # In this case the output sink is rooted at SYSTEM
2771 recovery_img_path = "vendor/etc/recovery.img"
2772 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2773 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002774
Tao Baof2cffbd2015-07-22 12:33:18 -07002775 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002776 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002777
2778 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002779 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002780 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002781 # With system-root-image, boot and recovery images will have mismatching
2782 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2783 # to handle such a case.
2784 if system_root_image:
2785 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002786 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002787 assert not os.path.exists(path)
2788 else:
2789 diff_program = ["imgdiff"]
2790 if os.path.exists(path):
2791 diff_program.append("-b")
2792 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002793 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002794 else:
2795 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002796
2797 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2798 _, _, patch = d.ComputePatch()
2799 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002800
Dan Albertebb19aa2015-03-27 19:11:53 -07002801 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002802 # The following GetTypeAndDevice()s need to use the path in the target
2803 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002804 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2805 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2806 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002807 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002808
Tao Baof2cffbd2015-07-22 12:33:18 -07002809 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002810
2811 # Note that we use /vendor to refer to the recovery resources. This will
2812 # work for a separate vendor partition mounted at /vendor or a
2813 # /system/vendor subdirectory on the system partition, for which init will
2814 # create a symlink from /vendor to /system/vendor.
2815
2816 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002817if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2818 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002819 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002820 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2821 log -t recovery "Installing new recovery image: succeeded" || \\
2822 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002823else
2824 log -t recovery "Recovery image already installed"
2825fi
2826""" % {'type': recovery_type,
2827 'device': recovery_device,
2828 'sha1': recovery_img.sha1,
2829 'size': recovery_img.size}
2830 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002831 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002832if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2833 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002834 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002835 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2836 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2837 log -t recovery "Installing new recovery image: succeeded" || \\
2838 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002839else
2840 log -t recovery "Recovery image already installed"
2841fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002842""" % {'boot_size': boot_img.size,
2843 'boot_sha1': boot_img.sha1,
2844 'recovery_size': recovery_img.size,
2845 'recovery_sha1': recovery_img.sha1,
2846 'boot_type': boot_type,
2847 'boot_device': boot_device,
2848 'recovery_type': recovery_type,
2849 'recovery_device': recovery_device,
2850 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002851
Bill Peckhame868aec2019-09-17 17:06:47 -07002852 # The install script location moved from /system/etc to /system/bin in the L
2853 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2854 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002855
Tao Bao32fcdab2018-10-12 10:30:39 -07002856 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002857
Tao Baoda30cfa2017-12-01 16:19:46 -08002858 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002859
2860
2861class DynamicPartitionUpdate(object):
2862 def __init__(self, src_group=None, tgt_group=None, progress=None,
2863 block_difference=None):
2864 self.src_group = src_group
2865 self.tgt_group = tgt_group
2866 self.progress = progress
2867 self.block_difference = block_difference
2868
2869 @property
2870 def src_size(self):
2871 if not self.block_difference:
2872 return 0
2873 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2874
2875 @property
2876 def tgt_size(self):
2877 if not self.block_difference:
2878 return 0
2879 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2880
2881 @staticmethod
2882 def _GetSparseImageSize(img):
2883 if not img:
2884 return 0
2885 return img.blocksize * img.total_blocks
2886
2887
2888class DynamicGroupUpdate(object):
2889 def __init__(self, src_size=None, tgt_size=None):
2890 # None: group does not exist. 0: no size limits.
2891 self.src_size = src_size
2892 self.tgt_size = tgt_size
2893
2894
2895class DynamicPartitionsDifference(object):
2896 def __init__(self, info_dict, block_diffs, progress_dict=None,
2897 source_info_dict=None):
2898 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002899 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002900
2901 self._remove_all_before_apply = False
2902 if source_info_dict is None:
2903 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002904 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002905
Tao Baof1113e92019-06-18 12:10:14 -07002906 block_diff_dict = collections.OrderedDict(
2907 [(e.partition, e) for e in block_diffs])
2908
Yifan Hong10c530d2018-12-27 17:34:18 -08002909 assert len(block_diff_dict) == len(block_diffs), \
2910 "Duplicated BlockDifference object for {}".format(
2911 [partition for partition, count in
2912 collections.Counter(e.partition for e in block_diffs).items()
2913 if count > 1])
2914
Yifan Hong79997e52019-01-23 16:56:19 -08002915 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002916
2917 for p, block_diff in block_diff_dict.items():
2918 self._partition_updates[p] = DynamicPartitionUpdate()
2919 self._partition_updates[p].block_difference = block_diff
2920
2921 for p, progress in progress_dict.items():
2922 if p in self._partition_updates:
2923 self._partition_updates[p].progress = progress
2924
2925 tgt_groups = shlex.split(info_dict.get(
2926 "super_partition_groups", "").strip())
2927 src_groups = shlex.split(source_info_dict.get(
2928 "super_partition_groups", "").strip())
2929
2930 for g in tgt_groups:
2931 for p in shlex.split(info_dict.get(
2932 "super_%s_partition_list" % g, "").strip()):
2933 assert p in self._partition_updates, \
2934 "{} is in target super_{}_partition_list but no BlockDifference " \
2935 "object is provided.".format(p, g)
2936 self._partition_updates[p].tgt_group = g
2937
2938 for g in src_groups:
2939 for p in shlex.split(source_info_dict.get(
2940 "super_%s_partition_list" % g, "").strip()):
2941 assert p in self._partition_updates, \
2942 "{} is in source super_{}_partition_list but no BlockDifference " \
2943 "object is provided.".format(p, g)
2944 self._partition_updates[p].src_group = g
2945
Yifan Hong45433e42019-01-18 13:55:25 -08002946 target_dynamic_partitions = set(shlex.split(info_dict.get(
2947 "dynamic_partition_list", "").strip()))
2948 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2949 if u.tgt_size)
2950 assert block_diffs_with_target == target_dynamic_partitions, \
2951 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2952 list(target_dynamic_partitions), list(block_diffs_with_target))
2953
2954 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2955 "dynamic_partition_list", "").strip()))
2956 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2957 if u.src_size)
2958 assert block_diffs_with_source == source_dynamic_partitions, \
2959 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2960 list(source_dynamic_partitions), list(block_diffs_with_source))
2961
Yifan Hong10c530d2018-12-27 17:34:18 -08002962 if self._partition_updates:
2963 logger.info("Updating dynamic partitions %s",
2964 self._partition_updates.keys())
2965
Yifan Hong79997e52019-01-23 16:56:19 -08002966 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002967
2968 for g in tgt_groups:
2969 self._group_updates[g] = DynamicGroupUpdate()
2970 self._group_updates[g].tgt_size = int(info_dict.get(
2971 "super_%s_group_size" % g, "0").strip())
2972
2973 for g in src_groups:
2974 if g not in self._group_updates:
2975 self._group_updates[g] = DynamicGroupUpdate()
2976 self._group_updates[g].src_size = int(source_info_dict.get(
2977 "super_%s_group_size" % g, "0").strip())
2978
2979 self._Compute()
2980
2981 def WriteScript(self, script, output_zip, write_verify_script=False):
2982 script.Comment('--- Start patching dynamic partitions ---')
2983 for p, u in self._partition_updates.items():
2984 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2985 script.Comment('Patch partition %s' % p)
2986 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2987 write_verify_script=False)
2988
2989 op_list_path = MakeTempFile()
2990 with open(op_list_path, 'w') as f:
2991 for line in self._op_list:
2992 f.write('{}\n'.format(line))
2993
2994 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2995
2996 script.Comment('Update dynamic partition metadata')
2997 script.AppendExtra('assert(update_dynamic_partitions('
2998 'package_extract_file("dynamic_partitions_op_list")));')
2999
3000 if write_verify_script:
3001 for p, u in self._partition_updates.items():
3002 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3003 u.block_difference.WritePostInstallVerifyScript(script)
3004 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3005
3006 for p, u in self._partition_updates.items():
3007 if u.tgt_size and u.src_size <= u.tgt_size:
3008 script.Comment('Patch partition %s' % p)
3009 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3010 write_verify_script=write_verify_script)
3011 if write_verify_script:
3012 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3013
3014 script.Comment('--- End patching dynamic partitions ---')
3015
3016 def _Compute(self):
3017 self._op_list = list()
3018
3019 def append(line):
3020 self._op_list.append(line)
3021
3022 def comment(line):
3023 self._op_list.append("# %s" % line)
3024
3025 if self._remove_all_before_apply:
3026 comment('Remove all existing dynamic partitions and groups before '
3027 'applying full OTA')
3028 append('remove_all_groups')
3029
3030 for p, u in self._partition_updates.items():
3031 if u.src_group and not u.tgt_group:
3032 append('remove %s' % p)
3033
3034 for p, u in self._partition_updates.items():
3035 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3036 comment('Move partition %s from %s to default' % (p, u.src_group))
3037 append('move %s default' % p)
3038
3039 for p, u in self._partition_updates.items():
3040 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3041 comment('Shrink partition %s from %d to %d' %
3042 (p, u.src_size, u.tgt_size))
3043 append('resize %s %s' % (p, u.tgt_size))
3044
3045 for g, u in self._group_updates.items():
3046 if u.src_size is not None and u.tgt_size is None:
3047 append('remove_group %s' % g)
3048 if (u.src_size is not None and u.tgt_size is not None and
3049 u.src_size > u.tgt_size):
3050 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3051 append('resize_group %s %d' % (g, u.tgt_size))
3052
3053 for g, u in self._group_updates.items():
3054 if u.src_size is None and u.tgt_size is not None:
3055 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3056 append('add_group %s %d' % (g, u.tgt_size))
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('Grow 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 p, u in self._partition_updates.items():
3063 if u.tgt_group and not u.src_group:
3064 comment('Add partition %s to group %s' % (p, u.tgt_group))
3065 append('add %s %s' % (p, u.tgt_group))
3066
3067 for p, u in self._partition_updates.items():
3068 if u.tgt_size and u.src_size < u.tgt_size:
3069 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3070 append('resize %s %d' % (p, u.tgt_size))
3071
3072 for p, u in self._partition_updates.items():
3073 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3074 comment('Move partition %s from default to %s' %
3075 (p, u.tgt_group))
3076 append('move %s %s' % (p, u.tgt_group))