blob: d73628f74c3a3323788de3711a3c7c82c2a7283c [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070020import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070021import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070022import getopt
23import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010024import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070025import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070026import json
27import logging
28import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070029import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080030import platform
Doug Zongkereef39442009-04-02 12:14:19 -070031import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070032import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070033import shutil
34import subprocess
35import sys
36import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070037import threading
38import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070039import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080040from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070041
Tianjie Xu41976c72019-07-03 13:57:01 -070042import images
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070044from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070045
Tao Bao32fcdab2018-10-12 10:30:39 -070046logger = logging.getLogger(__name__)
47
Tao Bao986ee862018-10-04 15:46:16 -070048
Dan Albert8b72aef2015-03-23 19:13:21 -070049class Options(object):
50 def __init__(self):
Pavel Salomatov32676552019-03-06 20:00:45 +030051 base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
52 if base_out_path is None:
53 base_search_path = "out"
54 else:
Tao Bao2cc0ca12019-03-15 10:44:43 -070055 base_search_path = os.path.join(base_out_path,
56 os.path.basename(os.getcwd()))
Pavel Salomatov32676552019-03-06 20:00:45 +030057
Tao Baoa3705452019-06-24 15:33:41 -070058 # Python >= 3.3 returns 'linux', whereas Python 2.7 gives 'linux2'.
Dan Albert8b72aef2015-03-23 19:13:21 -070059 platform_search_path = {
Tao Baoa3705452019-06-24 15:33:41 -070060 "linux": os.path.join(base_search_path, "host/linux-x86"),
Pavel Salomatov32676552019-03-06 20:00:45 +030061 "linux2": os.path.join(base_search_path, "host/linux-x86"),
62 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070063 }
Doug Zongker85448772014-09-09 14:59:20 -070064
Tao Bao76def242017-11-21 09:25:31 -080065 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070066 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080067 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070068 self.extra_signapk_args = []
69 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080070 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070071 self.public_key_suffix = ".x509.pem"
72 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070073 # use otatools built boot_signer by default
74 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070075 self.boot_signer_args = []
76 self.verity_signer_path = None
77 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070078 self.verbose = False
79 self.tempfiles = []
80 self.device_specific = None
81 self.extras = {}
82 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070083 self.source_info_dict = None
84 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070085 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070086 # Stash size cannot exceed cache_size * threshold.
87 self.cache_size = None
88 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070089
90
91OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070092
Tao Bao71197512018-10-11 14:08:45 -070093# The block size that's used across the releasetools scripts.
94BLOCK_SIZE = 4096
95
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080096# Values for "certificate" in apkcerts that mean special things.
97SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
98
Tao Bao5cc0abb2019-03-21 10:18:05 -070099# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
100# that system_other is not in the list because we don't want to include its
101# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900102AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Steve Mucklee1b10862019-07-10 10:49:37 -0700103 'system_ext', 'vendor', 'vendor_boot')
Tao Bao9dd909e2017-11-14 11:27:32 -0800104
Tao Bao08c190f2019-06-03 23:07:58 -0700105# Chained VBMeta partitions.
106AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
107
Tianjie Xu861f4132018-09-12 11:49:33 -0700108# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900109PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700110
111
Tianjie Xu209db462016-05-24 17:34:52 -0700112class ErrorCode(object):
113 """Define error_codes for failures that happen during the actual
114 update package installation.
115
116 Error codes 0-999 are reserved for failures before the package
117 installation (i.e. low battery, package verification failure).
118 Detailed code in 'bootable/recovery/error_code.h' """
119
120 SYSTEM_VERIFICATION_FAILURE = 1000
121 SYSTEM_UPDATE_FAILURE = 1001
122 SYSTEM_UNEXPECTED_CONTENTS = 1002
123 SYSTEM_NONZERO_CONTENTS = 1003
124 SYSTEM_RECOVER_FAILURE = 1004
125 VENDOR_VERIFICATION_FAILURE = 2000
126 VENDOR_UPDATE_FAILURE = 2001
127 VENDOR_UNEXPECTED_CONTENTS = 2002
128 VENDOR_NONZERO_CONTENTS = 2003
129 VENDOR_RECOVER_FAILURE = 2004
130 OEM_PROP_MISMATCH = 3000
131 FINGERPRINT_MISMATCH = 3001
132 THUMBPRINT_MISMATCH = 3002
133 OLDER_BUILD = 3003
134 DEVICE_MISMATCH = 3004
135 BAD_PATCH_FILE = 3005
136 INSUFFICIENT_CACHE_SPACE = 3006
137 TUNE_PARTITION_FAILURE = 3007
138 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800139
Tao Bao80921982018-03-21 21:02:19 -0700140
Dan Albert8b72aef2015-03-23 19:13:21 -0700141class ExternalError(RuntimeError):
142 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700143
144
Tao Bao32fcdab2018-10-12 10:30:39 -0700145def InitLogging():
146 DEFAULT_LOGGING_CONFIG = {
147 'version': 1,
148 'disable_existing_loggers': False,
149 'formatters': {
150 'standard': {
151 'format':
152 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
153 'datefmt': '%Y-%m-%d %H:%M:%S',
154 },
155 },
156 'handlers': {
157 'default': {
158 'class': 'logging.StreamHandler',
159 'formatter': 'standard',
160 },
161 },
162 'loggers': {
163 '': {
164 'handlers': ['default'],
165 'level': 'WARNING',
166 'propagate': True,
167 }
168 }
169 }
170 env_config = os.getenv('LOGGING_CONFIG')
171 if env_config:
172 with open(env_config) as f:
173 config = json.load(f)
174 else:
175 config = DEFAULT_LOGGING_CONFIG
176
177 # Increase the logging level for verbose mode.
178 if OPTIONS.verbose:
179 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
180 config['loggers']['']['level'] = 'INFO'
181
182 logging.config.dictConfig(config)
183
184
Tao Bao39451582017-05-04 11:10:47 -0700185def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700186 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700187
Tao Bao73dd4f42018-10-04 16:25:33 -0700188 Args:
189 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700190 verbose: Whether the commands should be shown. Default to the global
191 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700192 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
193 stdin, etc. stdout and stderr will default to subprocess.PIPE and
194 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800195 universal_newlines will default to True, as most of the users in
196 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700197
198 Returns:
199 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700200 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700201 if 'stdout' not in kwargs and 'stderr' not in kwargs:
202 kwargs['stdout'] = subprocess.PIPE
203 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800204 if 'universal_newlines' not in kwargs:
205 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700206 # Don't log any if caller explicitly says so.
207 if verbose != False:
208 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700209 return subprocess.Popen(args, **kwargs)
210
211
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800212def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800213 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800214
215 Args:
216 args: The command represented as a list of strings.
217 verbose: Whether the commands should be shown. Default to the global
218 verbosity if unspecified.
219 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
220 stdin, etc. stdout and stderr will default to subprocess.PIPE and
221 subprocess.STDOUT respectively unless caller specifies any of them.
222
Bill Peckham889b0c62019-02-21 18:53:37 -0800223 Raises:
224 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800225 """
226 proc = Run(args, verbose=verbose, **kwargs)
227 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800228
229 if proc.returncode != 0:
230 raise ExternalError(
231 "Failed to run command '{}' (exit code {})".format(
232 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800233
234
Tao Bao986ee862018-10-04 15:46:16 -0700235def RunAndCheckOutput(args, verbose=None, **kwargs):
236 """Runs the given command and returns the output.
237
238 Args:
239 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700240 verbose: Whether the commands should be shown. Default to the global
241 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700242 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
243 stdin, etc. stdout and stderr will default to subprocess.PIPE and
244 subprocess.STDOUT respectively unless caller specifies any of them.
245
246 Returns:
247 The output string.
248
249 Raises:
250 ExternalError: On non-zero exit from the command.
251 """
Tao Bao986ee862018-10-04 15:46:16 -0700252 proc = Run(args, verbose=verbose, **kwargs)
253 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800254 if output is None:
255 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700256 # Don't log any if caller explicitly says so.
257 if verbose != False:
258 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700259 if proc.returncode != 0:
260 raise ExternalError(
261 "Failed to run command '{}' (exit code {}):\n{}".format(
262 args, proc.returncode, output))
263 return output
264
265
Tao Baoc765cca2018-01-31 17:32:40 -0800266def RoundUpTo4K(value):
267 rounded_up = value + 4095
268 return rounded_up - (rounded_up % 4096)
269
270
Ying Wang7e6d4e42010-12-13 16:25:36 -0800271def CloseInheritedPipes():
272 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
273 before doing other work."""
274 if platform.system() != "Darwin":
275 return
276 for d in range(3, 1025):
277 try:
278 stat = os.fstat(d)
279 if stat is not None:
280 pipebit = stat[0] & 0x1000
281 if pipebit != 0:
282 os.close(d)
283 except OSError:
284 pass
285
286
Tao Bao1c320f82019-10-04 23:25:12 -0700287class BuildInfo(object):
288 """A class that holds the information for a given build.
289
290 This class wraps up the property querying for a given source or target build.
291 It abstracts away the logic of handling OEM-specific properties, and caches
292 the commonly used properties such as fingerprint.
293
294 There are two types of info dicts: a) build-time info dict, which is generated
295 at build time (i.e. included in a target_files zip); b) OEM info dict that is
296 specified at package generation time (via command line argument
297 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
298 having "oem_fingerprint_properties" in build-time info dict), all the queries
299 would be answered based on build-time info dict only. Otherwise if using
300 OEM-specific properties, some of them will be calculated from two info dicts.
301
302 Users can query properties similarly as using a dict() (e.g. info['fstab']),
303 or to query build properties via GetBuildProp() or GetVendorBuildProp().
304
305 Attributes:
306 info_dict: The build-time info dict.
307 is_ab: Whether it's a build that uses A/B OTA.
308 oem_dicts: A list of OEM dicts.
309 oem_props: A list of OEM properties that should be read from OEM dicts; None
310 if the build doesn't use any OEM-specific property.
311 fingerprint: The fingerprint of the build, which would be calculated based
312 on OEM properties if applicable.
313 device: The device name, which could come from OEM dicts if applicable.
314 """
315
316 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
317 "ro.product.manufacturer", "ro.product.model",
318 "ro.product.name"]
319 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
320 "system_ext", "system"]
321
Tao Bao3ed35d32019-10-07 20:48:48 -0700322 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700323 """Initializes a BuildInfo instance with the given dicts.
324
325 Note that it only wraps up the given dicts, without making copies.
326
327 Arguments:
328 info_dict: The build-time info dict.
329 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
330 that it always uses the first dict to calculate the fingerprint or the
331 device name. The rest would be used for asserting OEM properties only
332 (e.g. one package can be installed on one of these devices).
333
334 Raises:
335 ValueError: On invalid inputs.
336 """
337 self.info_dict = info_dict
338 self.oem_dicts = oem_dicts
339
340 self._is_ab = info_dict.get("ab_update") == "true"
341 self._oem_props = info_dict.get("oem_fingerprint_properties")
342
343 if self._oem_props:
344 assert oem_dicts, "OEM source required for this build"
345
346 # These two should be computed only after setting self._oem_props.
347 self._device = self.GetOemProperty("ro.product.device")
348 self._fingerprint = self.CalculateFingerprint()
349
350 # Sanity check the build fingerprint.
351 if (' ' in self._fingerprint or
352 any(ord(ch) > 127 for ch in self._fingerprint)):
353 raise ValueError(
354 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
355 '3.2.2. Build Parameters.'.format(self._fingerprint))
356
357 @property
358 def is_ab(self):
359 return self._is_ab
360
361 @property
362 def device(self):
363 return self._device
364
365 @property
366 def fingerprint(self):
367 return self._fingerprint
368
369 @property
370 def vendor_fingerprint(self):
371 return self._fingerprint_of("vendor")
372
373 @property
374 def product_fingerprint(self):
375 return self._fingerprint_of("product")
376
377 @property
378 def odm_fingerprint(self):
379 return self._fingerprint_of("odm")
380
381 def _fingerprint_of(self, partition):
382 if partition + ".build.prop" not in self.info_dict:
383 return None
384 build_prop = self.info_dict[partition + ".build.prop"]
385 if "ro." + partition + ".build.fingerprint" in build_prop:
386 return build_prop["ro." + partition + ".build.fingerprint"]
387 if "ro." + partition + ".build.thumbprint" in build_prop:
388 return build_prop["ro." + partition + ".build.thumbprint"]
389 return None
390
391 @property
392 def oem_props(self):
393 return self._oem_props
394
395 def __getitem__(self, key):
396 return self.info_dict[key]
397
398 def __setitem__(self, key, value):
399 self.info_dict[key] = value
400
401 def get(self, key, default=None):
402 return self.info_dict.get(key, default)
403
404 def items(self):
405 return self.info_dict.items()
406
407 def GetBuildProp(self, prop):
408 """Returns the inquired build property."""
409 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
410 return self._ResolveRoProductBuildProp(prop)
411
412 try:
413 return self.info_dict.get("build.prop", {})[prop]
414 except KeyError:
415 raise ExternalError("couldn't find %s in build.prop" % (prop,))
416
417 def _ResolveRoProductBuildProp(self, prop):
418 """Resolves the inquired ro.product.* build property"""
419 prop_val = self.info_dict.get("build.prop", {}).get(prop)
420 if prop_val:
421 return prop_val
422
423 source_order_val = self.info_dict.get("build.prop", {}).get(
424 "ro.product.property_source_order")
425 if source_order_val:
426 source_order = source_order_val.split(",")
427 else:
428 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
429
430 # Check that all sources in ro.product.property_source_order are valid
431 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
432 for x in source_order]):
433 raise ExternalError(
434 "Invalid ro.product.property_source_order '{}'".format(source_order))
435
436 for source in source_order:
437 source_prop = prop.replace(
438 "ro.product", "ro.product.{}".format(source), 1)
439 prop_val = self.info_dict.get(
440 "{}.build.prop".format(source), {}).get(source_prop)
441 if prop_val:
442 return prop_val
443
444 raise ExternalError("couldn't resolve {}".format(prop))
445
446 def GetVendorBuildProp(self, prop):
447 """Returns the inquired vendor build property."""
448 try:
449 return self.info_dict.get("vendor.build.prop", {})[prop]
450 except KeyError:
451 raise ExternalError(
452 "couldn't find %s in vendor.build.prop" % (prop,))
453
454 def GetOemProperty(self, key):
455 if self.oem_props is not None and key in self.oem_props:
456 return self.oem_dicts[0][key]
457 return self.GetBuildProp(key)
458
459 def CalculateFingerprint(self):
460 if self.oem_props is None:
461 try:
462 return self.GetBuildProp("ro.build.fingerprint")
463 except ExternalError:
464 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
465 self.GetBuildProp("ro.product.brand"),
466 self.GetBuildProp("ro.product.name"),
467 self.GetBuildProp("ro.product.device"),
468 self.GetBuildProp("ro.build.version.release"),
469 self.GetBuildProp("ro.build.id"),
470 self.GetBuildProp("ro.build.version.incremental"),
471 self.GetBuildProp("ro.build.type"),
472 self.GetBuildProp("ro.build.tags"))
473 return "%s/%s/%s:%s" % (
474 self.GetOemProperty("ro.product.brand"),
475 self.GetOemProperty("ro.product.name"),
476 self.GetOemProperty("ro.product.device"),
477 self.GetBuildProp("ro.build.thumbprint"))
478
479 def WriteMountOemScript(self, script):
480 assert self.oem_props is not None
481 recovery_mount_options = self.info_dict.get("recovery_mount_options")
482 script.Mount("/oem", recovery_mount_options)
483
484 def WriteDeviceAssertions(self, script, oem_no_mount):
485 # Read the property directly if not using OEM properties.
486 if not self.oem_props:
487 script.AssertDevice(self.device)
488 return
489
490 # Otherwise assert OEM properties.
491 if not self.oem_dicts:
492 raise ExternalError(
493 "No OEM file provided to answer expected assertions")
494
495 for prop in self.oem_props.split():
496 values = []
497 for oem_dict in self.oem_dicts:
498 if prop in oem_dict:
499 values.append(oem_dict[prop])
500 if not values:
501 raise ExternalError(
502 "The OEM file is missing the property %s" % (prop,))
503 script.AssertOemProperty(prop, values, oem_no_mount)
504
505
Tao Bao410ad8b2018-08-24 12:08:38 -0700506def LoadInfoDict(input_file, repacking=False):
507 """Loads the key/value pairs from the given input target_files.
508
509 It reads `META/misc_info.txt` file in the target_files input, does sanity
510 checks and returns the parsed key/value pairs for to the given build. It's
511 usually called early when working on input target_files files, e.g. when
512 generating OTAs, or signing builds. Note that the function may be called
513 against an old target_files file (i.e. from past dessert releases). So the
514 property parsing needs to be backward compatible.
515
516 In a `META/misc_info.txt`, a few properties are stored as links to the files
517 in the PRODUCT_OUT directory. It works fine with the build system. However,
518 they are no longer available when (re)generating images from target_files zip.
519 When `repacking` is True, redirect these properties to the actual files in the
520 unzipped directory.
521
522 Args:
523 input_file: The input target_files file, which could be an open
524 zipfile.ZipFile instance, or a str for the dir that contains the files
525 unzipped from a target_files file.
526 repacking: Whether it's trying repack an target_files file after loading the
527 info dict (default: False). If so, it will rewrite a few loaded
528 properties (e.g. selinux_fc, root_dir) to point to the actual files in
529 target_files file. When doing repacking, `input_file` must be a dir.
530
531 Returns:
532 A dict that contains the parsed key/value pairs.
533
534 Raises:
535 AssertionError: On invalid input arguments.
536 ValueError: On malformed input values.
537 """
538 if repacking:
539 assert isinstance(input_file, str), \
540 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700541
Doug Zongkerc9253822014-02-04 12:17:58 -0800542 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700543 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800544 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800545 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700546 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800547 try:
548 with open(path) as f:
549 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700550 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800551 if e.errno == errno.ENOENT:
552 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800553
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700554 try:
Michael Runge6e836112014-04-15 17:40:21 -0700555 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700556 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700557 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700558
Tao Bao410ad8b2018-08-24 12:08:38 -0700559 if "recovery_api_version" not in d:
560 raise ValueError("Failed to find 'recovery_api_version'")
561 if "fstab_version" not in d:
562 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800563
Tao Bao410ad8b2018-08-24 12:08:38 -0700564 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700565 # "selinux_fc" properties should point to the file_contexts files
566 # (file_contexts.bin) under META/.
567 for key in d:
568 if key.endswith("selinux_fc"):
569 fc_basename = os.path.basename(d[key])
570 fc_config = os.path.join(input_file, "META", fc_basename)
571 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700572
Daniel Norman72c626f2019-05-13 15:58:14 -0700573 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700574
Tom Cherryd14b8952018-08-09 14:26:00 -0700575 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700576 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700577 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700578 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700579
Tao Baof54216f2016-03-29 15:12:37 -0700580 # Redirect {system,vendor}_base_fs_file.
581 if "system_base_fs_file" in d:
582 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700583 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700584 if os.path.exists(system_base_fs_file):
585 d["system_base_fs_file"] = system_base_fs_file
586 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700587 logger.warning(
588 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700589 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700590
591 if "vendor_base_fs_file" in d:
592 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700593 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700594 if os.path.exists(vendor_base_fs_file):
595 d["vendor_base_fs_file"] = vendor_base_fs_file
596 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700597 logger.warning(
598 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700599 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700600
Doug Zongker37974732010-09-16 17:44:38 -0700601 def makeint(key):
602 if key in d:
603 d[key] = int(d[key], 0)
604
605 makeint("recovery_api_version")
606 makeint("blocksize")
607 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700608 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700609 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700610 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700611 makeint("recovery_size")
612 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800613 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700614
Tao Bao765668f2019-10-04 22:03:00 -0700615 # Load recovery fstab if applicable.
616 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800617
Tianjie Xu861f4132018-09-12 11:49:33 -0700618 # Tries to load the build props for all partitions with care_map, including
619 # system and vendor.
620 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800621 partition_prop = "{}.build.prop".format(partition)
622 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700623 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800624 # Some partition might use /<partition>/etc/build.prop as the new path.
625 # TODO: try new path first when majority of them switch to the new path.
626 if not d[partition_prop]:
627 d[partition_prop] = LoadBuildProp(
628 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700629 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800630
Tao Bao3ed35d32019-10-07 20:48:48 -0700631 # Set up the salt (based on fingerprint) that will be used when adding AVB
632 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800633 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700634 build_info = BuildInfo(d)
635 d["avb_salt"] = sha256(build_info.fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800636
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700637 return d
638
Tao Baod1de6f32017-03-01 16:38:48 -0800639
Tao Baobcd1d162017-08-26 13:10:26 -0700640def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700641 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700642 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700643 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700644 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700645 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700646 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700647
Tao Baod1de6f32017-03-01 16:38:48 -0800648
Daniel Norman4cc9df62019-07-18 10:11:07 -0700649def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900650 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700651 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900652
Daniel Norman4cc9df62019-07-18 10:11:07 -0700653
654def LoadDictionaryFromFile(file_path):
655 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900656 return LoadDictionaryFromLines(lines)
657
658
Michael Runge6e836112014-04-15 17:40:21 -0700659def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700660 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700661 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700662 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700663 if not line or line.startswith("#"):
664 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700665 if "=" in line:
666 name, value = line.split("=", 1)
667 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700668 return d
669
Tao Baod1de6f32017-03-01 16:38:48 -0800670
Tianjie Xucfa86222016-03-07 16:31:19 -0800671def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
672 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700673 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800674 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700675 self.mount_point = mount_point
676 self.fs_type = fs_type
677 self.device = device
678 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700679 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700680
681 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800682 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700683 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700684 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700685 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700686
Tao Baod1de6f32017-03-01 16:38:48 -0800687 assert fstab_version == 2
688
689 d = {}
690 for line in data.split("\n"):
691 line = line.strip()
692 if not line or line.startswith("#"):
693 continue
694
695 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
696 pieces = line.split()
697 if len(pieces) != 5:
698 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
699
700 # Ignore entries that are managed by vold.
701 options = pieces[4]
702 if "voldmanaged=" in options:
703 continue
704
705 # It's a good line, parse it.
706 length = 0
707 options = options.split(",")
708 for i in options:
709 if i.startswith("length="):
710 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800711 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800712 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700713 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800714
Tao Baod1de6f32017-03-01 16:38:48 -0800715 mount_flags = pieces[3]
716 # Honor the SELinux context if present.
717 context = None
718 for i in mount_flags.split(","):
719 if i.startswith("context="):
720 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800721
Tao Baod1de6f32017-03-01 16:38:48 -0800722 mount_point = pieces[1]
723 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
724 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800725
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700726 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700727 # system. Other areas assume system is always at "/system" so point /system
728 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700729 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800730 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700731 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700732 return d
733
734
Tao Bao765668f2019-10-04 22:03:00 -0700735def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
736 """Finds the path to recovery fstab and loads its contents."""
737 # recovery fstab is only meaningful when installing an update via recovery
738 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
739 if info_dict.get('ab_update') == 'true':
740 return None
741
742 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
743 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
744 # cases, since it may load the info_dict from an old build (e.g. when
745 # generating incremental OTAs from that build).
746 system_root_image = info_dict.get('system_root_image') == 'true'
747 if info_dict.get('no_recovery') != 'true':
748 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
749 if isinstance(input_file, zipfile.ZipFile):
750 if recovery_fstab_path not in input_file.namelist():
751 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
752 else:
753 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
754 if not os.path.exists(path):
755 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
756 return LoadRecoveryFSTab(
757 read_helper, info_dict['fstab_version'], recovery_fstab_path,
758 system_root_image)
759
760 if info_dict.get('recovery_as_boot') == 'true':
761 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
762 if isinstance(input_file, zipfile.ZipFile):
763 if recovery_fstab_path not in input_file.namelist():
764 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
765 else:
766 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
767 if not os.path.exists(path):
768 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
769 return LoadRecoveryFSTab(
770 read_helper, info_dict['fstab_version'], recovery_fstab_path,
771 system_root_image)
772
773 return None
774
775
Doug Zongker37974732010-09-16 17:44:38 -0700776def DumpInfoDict(d):
777 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700778 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700779
Dan Albert8b72aef2015-03-23 19:13:21 -0700780
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700781def MergeDynamicPartitionInfoDicts(framework_dict,
782 vendor_dict,
783 include_dynamic_partition_list=True,
784 size_prefix="",
785 size_suffix="",
786 list_prefix="",
787 list_suffix=""):
788 """Merges dynamic partition info variables.
789
790 Args:
791 framework_dict: The dictionary of dynamic partition info variables from the
792 partial framework target files.
793 vendor_dict: The dictionary of dynamic partition info variables from the
794 partial vendor target files.
795 include_dynamic_partition_list: If true, merges the dynamic_partition_list
796 variable. Not all use cases need this variable merged.
797 size_prefix: The prefix in partition group size variables that precedes the
798 name of the partition group. For example, partition group 'group_a' with
799 corresponding size variable 'super_group_a_group_size' would have the
800 size_prefix 'super_'.
801 size_suffix: Similar to size_prefix but for the variable's suffix. For
802 example, 'super_group_a_group_size' would have size_suffix '_group_size'.
803 list_prefix: Similar to size_prefix but for the partition group's
804 partition_list variable.
805 list_suffix: Similar to size_suffix but for the partition group's
806 partition_list variable.
807
808 Returns:
809 The merged dynamic partition info dictionary.
810 """
811 merged_dict = {}
812 # Partition groups and group sizes are defined by the vendor dict because
813 # these values may vary for each board that uses a shared system image.
814 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
815 if include_dynamic_partition_list:
816 framework_dynamic_partition_list = framework_dict.get(
817 "dynamic_partition_list", "")
818 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list",
819 "")
820 merged_dict["dynamic_partition_list"] = (
821 "%s %s" % (framework_dynamic_partition_list,
822 vendor_dynamic_partition_list)).strip()
823 for partition_group in merged_dict["super_partition_groups"].split(" "):
824 # Set the partition group's size using the value from the vendor dict.
825 key = "%s%s%s" % (size_prefix, partition_group, size_suffix)
826 if key not in vendor_dict:
827 raise ValueError("Vendor dict does not contain required key %s." % key)
828 merged_dict[key] = vendor_dict[key]
829
830 # Set the partition group's partition list using a concatenation of the
831 # framework and vendor partition lists.
832 key = "%s%s%s" % (list_prefix, partition_group, list_suffix)
833 merged_dict[key] = (
834 "%s %s" %
835 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
836 return merged_dict
837
838
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800839def AppendAVBSigningArgs(cmd, partition):
840 """Append signing arguments for avbtool."""
841 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
842 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700843 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
844 new_key_path = os.path.join(OPTIONS.search_path, key_path)
845 if os.path.exists(new_key_path):
846 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800847 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
848 if key_path and algorithm:
849 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700850 avb_salt = OPTIONS.info_dict.get("avb_salt")
851 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700852 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700853 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800854
855
Tao Bao765668f2019-10-04 22:03:00 -0700856def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700857 """Returns the VBMeta arguments for partition.
858
859 It sets up the VBMeta argument by including the partition descriptor from the
860 given 'image', or by configuring the partition as a chained partition.
861
862 Args:
863 partition: The name of the partition (e.g. "system").
864 image: The path to the partition image.
865 info_dict: A dict returned by common.LoadInfoDict(). Will use
866 OPTIONS.info_dict if None has been given.
867
868 Returns:
869 A list of VBMeta arguments.
870 """
871 if info_dict is None:
872 info_dict = OPTIONS.info_dict
873
874 # Check if chain partition is used.
875 key_path = info_dict.get("avb_" + partition + "_key_path")
876 if key_path:
877 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
878 return ["--chain_partition", chained_partition_arg]
Tao Bao3ed35d32019-10-07 20:48:48 -0700879 return ["--include_descriptors_from_image", image]
Daniel Norman276f0622019-07-26 14:13:51 -0700880
881
Tao Bao02a08592018-07-22 12:40:45 -0700882def GetAvbChainedPartitionArg(partition, info_dict, key=None):
883 """Constructs and returns the arg to build or verify a chained partition.
884
885 Args:
886 partition: The partition name.
887 info_dict: The info dict to look up the key info and rollback index
888 location.
889 key: The key to be used for building or verifying the partition. Defaults to
890 the key listed in info_dict.
891
892 Returns:
893 A string of form "partition:rollback_index_location:key" that can be used to
894 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700895 """
896 if key is None:
897 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700898 if key and not os.path.exists(key) and OPTIONS.search_path:
899 new_key_path = os.path.join(OPTIONS.search_path, key)
900 if os.path.exists(new_key_path):
901 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700902 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700903 rollback_index_location = info_dict[
904 "avb_" + partition + "_rollback_index_location"]
905 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
906
907
Daniel Norman276f0622019-07-26 14:13:51 -0700908def BuildVBMeta(image_path, partitions, name, needed_partitions):
909 """Creates a VBMeta image.
910
911 It generates the requested VBMeta image. The requested image could be for
912 top-level or chained VBMeta image, which is determined based on the name.
913
914 Args:
915 image_path: The output path for the new VBMeta image.
916 partitions: A dict that's keyed by partition names with image paths as
917 values. Only valid partition names are accepted, as listed in
918 common.AVB_PARTITIONS.
919 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
920 needed_partitions: Partitions whose descriptors should be included into the
921 generated VBMeta image.
922
923 Raises:
924 AssertionError: On invalid input args.
925 """
926 avbtool = OPTIONS.info_dict["avb_avbtool"]
927 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
928 AppendAVBSigningArgs(cmd, name)
929
930 for partition, path in partitions.items():
931 if partition not in needed_partitions:
932 continue
933 assert (partition in AVB_PARTITIONS or
934 partition in AVB_VBMETA_PARTITIONS), \
935 'Unknown partition: {}'.format(partition)
936 assert os.path.exists(path), \
937 'Failed to find {} for {}'.format(path, partition)
938 cmd.extend(GetAvbPartitionArg(partition, path))
939
940 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
941 if args and args.strip():
942 split_args = shlex.split(args)
943 for index, arg in enumerate(split_args[:-1]):
944 # Sanity check that the image file exists. Some images might be defined
945 # as a path relative to source tree, which may not be available at the
946 # same location when running this script (we have the input target_files
947 # zip only). For such cases, we additionally scan other locations (e.g.
948 # IMAGES/, RADIO/, etc) before bailing out.
949 if arg == '--include_descriptors_from_image':
950 image_path = split_args[index + 1]
951 if os.path.exists(image_path):
952 continue
953 found = False
954 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
955 alt_path = os.path.join(
956 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
957 if os.path.exists(alt_path):
958 split_args[index + 1] = alt_path
959 found = True
960 break
961 assert found, 'Failed to find {}'.format(image_path)
962 cmd.extend(split_args)
963
964 RunAndCheckOutput(cmd)
965
966
Steve Mucklee1b10862019-07-10 10:49:37 -0700967def _MakeRamdisk(sourcedir, fs_config_file=None):
968 ramdisk_img = tempfile.NamedTemporaryFile()
969
970 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
971 cmd = ["mkbootfs", "-f", fs_config_file,
972 os.path.join(sourcedir, "RAMDISK")]
973 else:
974 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
975 p1 = Run(cmd, stdout=subprocess.PIPE)
976 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
977
978 p2.wait()
979 p1.wait()
980 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
981 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
982
983 return ramdisk_img
984
985
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700986def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800987 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700988 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700989
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700990 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800991 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
992 we are building a two-step special image (i.e. building a recovery image to
993 be loaded into /boot in two-step OTAs).
994
995 Return the image data, or None if sourcedir does not appear to contains files
996 for building the requested image.
997 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700998
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700999 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
1000 return None
1001
1002 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001003 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001004
Doug Zongkerd5131602012-08-02 14:46:42 -07001005 if info_dict is None:
1006 info_dict = OPTIONS.info_dict
1007
Doug Zongkereef39442009-04-02 12:14:19 -07001008 img = tempfile.NamedTemporaryFile()
1009
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001010 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001011 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001012
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001013 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1014 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1015
1016 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -07001017
Benoit Fradina45a8682014-07-14 21:00:43 +02001018 fn = os.path.join(sourcedir, "second")
1019 if os.access(fn, os.F_OK):
1020 cmd.append("--second")
1021 cmd.append(fn)
1022
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001023 fn = os.path.join(sourcedir, "dtb")
1024 if os.access(fn, os.F_OK):
1025 cmd.append("--dtb")
1026 cmd.append(fn)
1027
Doug Zongker171f1cd2009-06-15 22:36:37 -07001028 fn = os.path.join(sourcedir, "cmdline")
1029 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001030 cmd.append("--cmdline")
1031 cmd.append(open(fn).read().rstrip("\n"))
1032
1033 fn = os.path.join(sourcedir, "base")
1034 if os.access(fn, os.F_OK):
1035 cmd.append("--base")
1036 cmd.append(open(fn).read().rstrip("\n"))
1037
Ying Wang4de6b5b2010-08-25 14:29:34 -07001038 fn = os.path.join(sourcedir, "pagesize")
1039 if os.access(fn, os.F_OK):
1040 cmd.append("--pagesize")
1041 cmd.append(open(fn).read().rstrip("\n"))
1042
Tao Bao76def242017-11-21 09:25:31 -08001043 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001044 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001045 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001046
Tao Bao76def242017-11-21 09:25:31 -08001047 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001048 if args and args.strip():
1049 cmd.extend(shlex.split(args))
1050
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001051 if has_ramdisk:
1052 cmd.extend(["--ramdisk", ramdisk_img.name])
1053
Tao Baod95e9fd2015-03-29 23:07:41 -07001054 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001055 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001056 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001057 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001058 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001059 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001060
Tao Baobf70c3182017-07-11 17:27:55 -07001061 # "boot" or "recovery", without extension.
1062 partition_name = os.path.basename(sourcedir).lower()
1063
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001064 if partition_name == "recovery":
1065 if info_dict.get("include_recovery_dtbo") == "true":
1066 fn = os.path.join(sourcedir, "recovery_dtbo")
1067 cmd.extend(["--recovery_dtbo", fn])
1068 if info_dict.get("include_recovery_acpio") == "true":
1069 fn = os.path.join(sourcedir, "recovery_acpio")
1070 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001071
Tao Bao986ee862018-10-04 15:46:16 -07001072 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001073
Tao Bao76def242017-11-21 09:25:31 -08001074 if (info_dict.get("boot_signer") == "true" and
1075 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001076 # Hard-code the path as "/boot" for two-step special recovery image (which
1077 # will be loaded into /boot during the two-step OTA).
1078 if two_step_image:
1079 path = "/boot"
1080 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001081 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001082 cmd = [OPTIONS.boot_signer_path]
1083 cmd.extend(OPTIONS.boot_signer_args)
1084 cmd.extend([path, img.name,
1085 info_dict["verity_key"] + ".pk8",
1086 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001087 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001088
Tao Baod95e9fd2015-03-29 23:07:41 -07001089 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001090 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001091 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001092 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001093 # We have switched from the prebuilt futility binary to using the tool
1094 # (futility-host) built from the source. Override the setting in the old
1095 # TF.zip.
1096 futility = info_dict["futility"]
1097 if futility.startswith("prebuilts/"):
1098 futility = "futility-host"
1099 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001100 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001101 info_dict["vboot_key"] + ".vbprivk",
1102 info_dict["vboot_subkey"] + ".vbprivk",
1103 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001104 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001105 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001106
Tao Baof3282b42015-04-01 11:21:55 -07001107 # Clean up the temp files.
1108 img_unsigned.close()
1109 img_keyblock.close()
1110
David Zeuthen8fecb282017-12-01 16:24:01 -05001111 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001112 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001113 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001114 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001115 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001116 "--partition_size", str(part_size), "--partition_name",
1117 partition_name]
1118 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001119 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001120 if args and args.strip():
1121 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001122 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001123
1124 img.seek(os.SEEK_SET, 0)
1125 data = img.read()
1126
1127 if has_ramdisk:
1128 ramdisk_img.close()
1129 img.close()
1130
1131 return data
1132
1133
Doug Zongkerd5131602012-08-02 14:46:42 -07001134def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001135 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001136 """Return a File object with the desired bootable image.
1137
1138 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1139 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1140 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001141
Doug Zongker55d93282011-01-25 17:03:34 -08001142 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1143 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001144 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001145 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001146
1147 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1148 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001149 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001150 return File.FromLocalFile(name, prebuilt_path)
1151
Tao Bao32fcdab2018-10-12 10:30:39 -07001152 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001153
1154 if info_dict is None:
1155 info_dict = OPTIONS.info_dict
1156
1157 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001158 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1159 # for recovery.
1160 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1161 prebuilt_name != "boot.img" or
1162 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001163
Doug Zongker6f1d0312014-08-22 08:07:12 -07001164 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001165 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
1166 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001167 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001168 if data:
1169 return File(name, data)
1170 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001171
Doug Zongkereef39442009-04-02 12:14:19 -07001172
Steve Mucklee1b10862019-07-10 10:49:37 -07001173def _BuildVendorBootImage(sourcedir, info_dict=None):
1174 """Build a vendor boot image from the specified sourcedir.
1175
1176 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1177 turn them into a vendor boot image.
1178
1179 Return the image data, or None if sourcedir does not appear to contains files
1180 for building the requested image.
1181 """
1182
1183 if info_dict is None:
1184 info_dict = OPTIONS.info_dict
1185
1186 img = tempfile.NamedTemporaryFile()
1187
1188 ramdisk_img = _MakeRamdisk(sourcedir)
1189
1190 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1191 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1192
1193 cmd = [mkbootimg]
1194
1195 fn = os.path.join(sourcedir, "dtb")
1196 if os.access(fn, os.F_OK):
1197 cmd.append("--dtb")
1198 cmd.append(fn)
1199
1200 fn = os.path.join(sourcedir, "vendor_cmdline")
1201 if os.access(fn, os.F_OK):
1202 cmd.append("--vendor_cmdline")
1203 cmd.append(open(fn).read().rstrip("\n"))
1204
1205 fn = os.path.join(sourcedir, "base")
1206 if os.access(fn, os.F_OK):
1207 cmd.append("--base")
1208 cmd.append(open(fn).read().rstrip("\n"))
1209
1210 fn = os.path.join(sourcedir, "pagesize")
1211 if os.access(fn, os.F_OK):
1212 cmd.append("--pagesize")
1213 cmd.append(open(fn).read().rstrip("\n"))
1214
1215 args = info_dict.get("mkbootimg_args")
1216 if args and args.strip():
1217 cmd.extend(shlex.split(args))
1218
1219 args = info_dict.get("mkbootimg_version_args")
1220 if args and args.strip():
1221 cmd.extend(shlex.split(args))
1222
1223 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1224 cmd.extend(["--vendor_boot", img.name])
1225
1226 RunAndCheckOutput(cmd)
1227
1228 # AVB: if enabled, calculate and add hash.
1229 if info_dict.get("avb_enable") == "true":
1230 avbtool = info_dict["avb_avbtool"]
1231 part_size = info_dict["vendor_boot_size"]
1232 cmd = [avbtool, "add_hash_footer", "--image", img.name,
1233 "--partition_size", str(part_size), "--partition_name vendor_boot"]
1234 AppendAVBSigningArgs(cmd, "vendor_boot")
1235 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1236 if args and args.strip():
1237 cmd.extend(shlex.split(args))
1238 RunAndCheckOutput(cmd)
1239
1240 img.seek(os.SEEK_SET, 0)
1241 data = img.read()
1242
1243 ramdisk_img.close()
1244 img.close()
1245
1246 return data
1247
1248
1249def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1250 info_dict=None):
1251 """Return a File object with the desired vendor boot image.
1252
1253 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1254 the source files in 'unpack_dir'/'tree_subdir'."""
1255
1256 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1257 if os.path.exists(prebuilt_path):
1258 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1259 return File.FromLocalFile(name, prebuilt_path)
1260
1261 logger.info("building image from target_files %s...", tree_subdir)
1262
1263 if info_dict is None:
1264 info_dict = OPTIONS.info_dict
1265
1266 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1267 if data:
1268 return File(name, data)
1269 return None
1270
1271
Narayan Kamatha07bf042017-08-14 14:49:21 +01001272def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001273 """Gunzips the given gzip compressed file to a given output file."""
1274 with gzip.open(in_filename, "rb") as in_file, \
1275 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001276 shutil.copyfileobj(in_file, out_file)
1277
1278
Tao Bao0ff15de2019-03-20 11:26:06 -07001279def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001280 """Unzips the archive to the given directory.
1281
1282 Args:
1283 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001284 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001285 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1286 archvie. Non-matching patterns will be filtered out. If there's no match
1287 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001288 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001289 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001290 if patterns is not None:
1291 # Filter out non-matching patterns. unzip will complain otherwise.
1292 with zipfile.ZipFile(filename) as input_zip:
1293 names = input_zip.namelist()
1294 filtered = [
1295 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1296
1297 # There isn't any matching files. Don't unzip anything.
1298 if not filtered:
1299 return
1300 cmd.extend(filtered)
1301
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001302 RunAndCheckOutput(cmd)
1303
1304
Doug Zongker75f17362009-12-08 13:46:44 -08001305def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001306 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001307
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001308 Args:
1309 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1310 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1311
1312 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1313 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001314
Tao Bao1c830bf2017-12-25 10:43:47 -08001315 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001316 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001317 """
Doug Zongkereef39442009-04-02 12:14:19 -07001318
Tao Bao1c830bf2017-12-25 10:43:47 -08001319 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001320 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1321 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001322 UnzipToDir(m.group(1), tmp, pattern)
1323 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001324 filename = m.group(1)
1325 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001326 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001327
Tao Baodba59ee2018-01-09 13:21:02 -08001328 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001329
1330
Yifan Hong8a66a712019-04-04 15:37:57 -07001331def GetUserImage(which, tmpdir, input_zip,
1332 info_dict=None,
1333 allow_shared_blocks=None,
1334 hashtree_info_generator=None,
1335 reset_file_map=False):
1336 """Returns an Image object suitable for passing to BlockImageDiff.
1337
1338 This function loads the specified image from the given path. If the specified
1339 image is sparse, it also performs additional processing for OTA purpose. For
1340 example, it always adds block 0 to clobbered blocks list. It also detects
1341 files that cannot be reconstructed from the block list, for whom we should
1342 avoid applying imgdiff.
1343
1344 Args:
1345 which: The partition name.
1346 tmpdir: The directory that contains the prebuilt image and block map file.
1347 input_zip: The target-files ZIP archive.
1348 info_dict: The dict to be looked up for relevant info.
1349 allow_shared_blocks: If image is sparse, whether having shared blocks is
1350 allowed. If none, it is looked up from info_dict.
1351 hashtree_info_generator: If present and image is sparse, generates the
1352 hashtree_info for this sparse image.
1353 reset_file_map: If true and image is sparse, reset file map before returning
1354 the image.
1355 Returns:
1356 A Image object. If it is a sparse image and reset_file_map is False, the
1357 image will have file_map info loaded.
1358 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001359 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001360 info_dict = LoadInfoDict(input_zip)
1361
1362 is_sparse = info_dict.get("extfs_sparse_flag")
1363
1364 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1365 # shared blocks (i.e. some blocks will show up in multiple files' block
1366 # list). We can only allocate such shared blocks to the first "owner", and
1367 # disable imgdiff for all later occurrences.
1368 if allow_shared_blocks is None:
1369 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1370
1371 if is_sparse:
1372 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1373 hashtree_info_generator)
1374 if reset_file_map:
1375 img.ResetFileMap()
1376 return img
1377 else:
1378 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1379
1380
1381def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1382 """Returns a Image object suitable for passing to BlockImageDiff.
1383
1384 This function loads the specified non-sparse image from the given path.
1385
1386 Args:
1387 which: The partition name.
1388 tmpdir: The directory that contains the prebuilt image and block map file.
1389 Returns:
1390 A Image object.
1391 """
1392 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1393 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1394
1395 # The image and map files must have been created prior to calling
1396 # ota_from_target_files.py (since LMP).
1397 assert os.path.exists(path) and os.path.exists(mappath)
1398
Tianjie Xu41976c72019-07-03 13:57:01 -07001399 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1400
Yifan Hong8a66a712019-04-04 15:37:57 -07001401
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001402def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1403 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001404 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1405
1406 This function loads the specified sparse image from the given path, and
1407 performs additional processing for OTA purpose. For example, it always adds
1408 block 0 to clobbered blocks list. It also detects files that cannot be
1409 reconstructed from the block list, for whom we should avoid applying imgdiff.
1410
1411 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001412 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001413 tmpdir: The directory that contains the prebuilt image and block map file.
1414 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001415 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001416 hashtree_info_generator: If present, generates the hashtree_info for this
1417 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001418 Returns:
1419 A SparseImage object, with file_map info loaded.
1420 """
Tao Baoc765cca2018-01-31 17:32:40 -08001421 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1422 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1423
1424 # The image and map files must have been created prior to calling
1425 # ota_from_target_files.py (since LMP).
1426 assert os.path.exists(path) and os.path.exists(mappath)
1427
1428 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1429 # it to clobbered_blocks so that it will be written to the target
1430 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1431 clobbered_blocks = "0"
1432
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001433 image = sparse_img.SparseImage(
1434 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1435 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001436
1437 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1438 # if they contain all zeros. We can't reconstruct such a file from its block
1439 # list. Tag such entries accordingly. (Bug: 65213616)
1440 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001441 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001442 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001443 continue
1444
Tom Cherryd14b8952018-08-09 14:26:00 -07001445 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1446 # filename listed in system.map may contain an additional leading slash
1447 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1448 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001449 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001450
Tom Cherryd14b8952018-08-09 14:26:00 -07001451 # Special handling another case, where files not under /system
1452 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001453 if which == 'system' and not arcname.startswith('SYSTEM'):
1454 arcname = 'ROOT/' + arcname
1455
1456 assert arcname in input_zip.namelist(), \
1457 "Failed to find the ZIP entry for {}".format(entry)
1458
Tao Baoc765cca2018-01-31 17:32:40 -08001459 info = input_zip.getinfo(arcname)
1460 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001461
1462 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001463 # image, check the original block list to determine its completeness. Note
1464 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001465 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001466 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001467
Tao Baoc765cca2018-01-31 17:32:40 -08001468 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1469 ranges.extra['incomplete'] = True
1470
1471 return image
1472
1473
Doug Zongkereef39442009-04-02 12:14:19 -07001474def GetKeyPasswords(keylist):
1475 """Given a list of keys, prompt the user to enter passwords for
1476 those which require them. Return a {key: password} dict. password
1477 will be None if the key has no password."""
1478
Doug Zongker8ce7c252009-05-22 13:34:54 -07001479 no_passwords = []
1480 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001481 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001482 devnull = open("/dev/null", "w+b")
1483 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001484 # We don't need a password for things that aren't really keys.
1485 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001486 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001487 continue
1488
T.R. Fullhart37e10522013-03-18 10:31:26 -07001489 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001490 "-inform", "DER", "-nocrypt"],
1491 stdin=devnull.fileno(),
1492 stdout=devnull.fileno(),
1493 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001494 p.communicate()
1495 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001496 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001497 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001498 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001499 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1500 "-inform", "DER", "-passin", "pass:"],
1501 stdin=devnull.fileno(),
1502 stdout=devnull.fileno(),
1503 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001504 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001505 if p.returncode == 0:
1506 # Encrypted key with empty string as password.
1507 key_passwords[k] = ''
1508 elif stderr.startswith('Error decrypting key'):
1509 # Definitely encrypted key.
1510 # It would have said "Error reading key" if it didn't parse correctly.
1511 need_passwords.append(k)
1512 else:
1513 # Potentially, a type of key that openssl doesn't understand.
1514 # We'll let the routines in signapk.jar handle it.
1515 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001516 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001517
T.R. Fullhart37e10522013-03-18 10:31:26 -07001518 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001519 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001520 return key_passwords
1521
1522
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001523def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001524 """Gets the minSdkVersion declared in the APK.
1525
changho.shin0f125362019-07-08 10:59:00 +09001526 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001527 This can be both a decimal number (API Level) or a codename.
1528
1529 Args:
1530 apk_name: The APK filename.
1531
1532 Returns:
1533 The parsed SDK version string.
1534
1535 Raises:
1536 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001537 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001538 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001539 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001540 stderr=subprocess.PIPE)
1541 stdoutdata, stderrdata = proc.communicate()
1542 if proc.returncode != 0:
1543 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001544 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001545 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001546
Tao Baof47bf0f2018-03-21 23:28:51 -07001547 for line in stdoutdata.split("\n"):
1548 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001549 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1550 if m:
1551 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001552 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001553
1554
1555def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001556 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001557
Tao Baof47bf0f2018-03-21 23:28:51 -07001558 If minSdkVersion is set to a codename, it is translated to a number using the
1559 provided map.
1560
1561 Args:
1562 apk_name: The APK filename.
1563
1564 Returns:
1565 The parsed SDK version number.
1566
1567 Raises:
1568 ExternalError: On failing to get the min SDK version number.
1569 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001570 version = GetMinSdkVersion(apk_name)
1571 try:
1572 return int(version)
1573 except ValueError:
1574 # Not a decimal number. Codename?
1575 if version in codename_to_api_level_map:
1576 return codename_to_api_level_map[version]
1577 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001578 raise ExternalError(
1579 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1580 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001581
1582
1583def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001584 codename_to_api_level_map=None, whole_file=False,
1585 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001586 """Sign the input_name zip/jar/apk, producing output_name. Use the
1587 given key and password (the latter may be None if the key does not
1588 have a password.
1589
Doug Zongker951495f2009-08-14 12:44:19 -07001590 If whole_file is true, use the "-w" option to SignApk to embed a
1591 signature that covers the whole file in the archive comment of the
1592 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001593
1594 min_api_level is the API Level (int) of the oldest platform this file may end
1595 up on. If not specified for an APK, the API Level is obtained by interpreting
1596 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1597
1598 codename_to_api_level_map is needed to translate the codename which may be
1599 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001600
1601 Caller may optionally specify extra args to be passed to SignApk, which
1602 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001603 """
Tao Bao76def242017-11-21 09:25:31 -08001604 if codename_to_api_level_map is None:
1605 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001606 if extra_signapk_args is None:
1607 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001608
Alex Klyubin9667b182015-12-10 13:38:50 -08001609 java_library_path = os.path.join(
1610 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1611
Tao Baoe95540e2016-11-08 12:08:53 -08001612 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1613 ["-Djava.library.path=" + java_library_path,
1614 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001615 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001616 if whole_file:
1617 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001618
1619 min_sdk_version = min_api_level
1620 if min_sdk_version is None:
1621 if not whole_file:
1622 min_sdk_version = GetMinSdkVersionInt(
1623 input_name, codename_to_api_level_map)
1624 if min_sdk_version is not None:
1625 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1626
T.R. Fullhart37e10522013-03-18 10:31:26 -07001627 cmd.extend([key + OPTIONS.public_key_suffix,
1628 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001629 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001630
Tao Bao73dd4f42018-10-04 16:25:33 -07001631 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001632 if password is not None:
1633 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001634 stdoutdata, _ = proc.communicate(password)
1635 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001636 raise ExternalError(
1637 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001638 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001639
Doug Zongkereef39442009-04-02 12:14:19 -07001640
Doug Zongker37974732010-09-16 17:44:38 -07001641def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001642 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001643
Tao Bao9dd909e2017-11-14 11:27:32 -08001644 For non-AVB images, raise exception if the data is too big. Print a warning
1645 if the data is nearing the maximum size.
1646
1647 For AVB images, the actual image size should be identical to the limit.
1648
1649 Args:
1650 data: A string that contains all the data for the partition.
1651 target: The partition name. The ".img" suffix is optional.
1652 info_dict: The dict to be looked up for relevant info.
1653 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001654 if target.endswith(".img"):
1655 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001656 mount_point = "/" + target
1657
Ying Wangf8824af2014-06-03 14:07:27 -07001658 fs_type = None
1659 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001660 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001661 if mount_point == "/userdata":
1662 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001663 p = info_dict["fstab"][mount_point]
1664 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001665 device = p.device
1666 if "/" in device:
1667 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001668 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001669 if not fs_type or not limit:
1670 return
Doug Zongkereef39442009-04-02 12:14:19 -07001671
Andrew Boie0f9aec82012-02-14 09:32:52 -08001672 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001673 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1674 # path.
1675 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1676 if size != limit:
1677 raise ExternalError(
1678 "Mismatching image size for %s: expected %d actual %d" % (
1679 target, limit, size))
1680 else:
1681 pct = float(size) * 100.0 / limit
1682 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1683 if pct >= 99.0:
1684 raise ExternalError(msg)
1685 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001686 logger.warning("\n WARNING: %s\n", msg)
1687 else:
1688 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001689
1690
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001691def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001692 """Parses the APK certs info from a given target-files zip.
1693
1694 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1695 tuple with the following elements: (1) a dictionary that maps packages to
1696 certs (based on the "certificate" and "private_key" attributes in the file;
1697 (2) a string representing the extension of compressed APKs in the target files
1698 (e.g ".gz", ".bro").
1699
1700 Args:
1701 tf_zip: The input target_files ZipFile (already open).
1702
1703 Returns:
1704 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1705 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1706 no compressed APKs.
1707 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001708 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001709 compressed_extension = None
1710
Tao Bao0f990332017-09-08 19:02:54 -07001711 # META/apkcerts.txt contains the info for _all_ the packages known at build
1712 # time. Filter out the ones that are not installed.
1713 installed_files = set()
1714 for name in tf_zip.namelist():
1715 basename = os.path.basename(name)
1716 if basename:
1717 installed_files.add(basename)
1718
Tao Baoda30cfa2017-12-01 16:19:46 -08001719 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001720 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001721 if not line:
1722 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001723 m = re.match(
1724 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1725 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1726 line)
1727 if not m:
1728 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001729
Tao Bao818ddf52018-01-05 11:17:34 -08001730 matches = m.groupdict()
1731 cert = matches["CERT"]
1732 privkey = matches["PRIVKEY"]
1733 name = matches["NAME"]
1734 this_compressed_extension = matches["COMPRESSED"]
1735
1736 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1737 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1738 if cert in SPECIAL_CERT_STRINGS and not privkey:
1739 certmap[name] = cert
1740 elif (cert.endswith(OPTIONS.public_key_suffix) and
1741 privkey.endswith(OPTIONS.private_key_suffix) and
1742 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1743 certmap[name] = cert[:-public_key_suffix_len]
1744 else:
1745 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1746
1747 if not this_compressed_extension:
1748 continue
1749
1750 # Only count the installed files.
1751 filename = name + '.' + this_compressed_extension
1752 if filename not in installed_files:
1753 continue
1754
1755 # Make sure that all the values in the compression map have the same
1756 # extension. We don't support multiple compression methods in the same
1757 # system image.
1758 if compressed_extension:
1759 if this_compressed_extension != compressed_extension:
1760 raise ValueError(
1761 "Multiple compressed extensions: {} vs {}".format(
1762 compressed_extension, this_compressed_extension))
1763 else:
1764 compressed_extension = this_compressed_extension
1765
1766 return (certmap,
1767 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001768
1769
Doug Zongkereef39442009-04-02 12:14:19 -07001770COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001771Global options
1772
1773 -p (--path) <dir>
1774 Prepend <dir>/bin to the list of places to search for binaries run by this
1775 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001776
Doug Zongker05d3dea2009-06-22 11:32:31 -07001777 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001778 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001779
Tao Bao30df8b42018-04-23 15:32:53 -07001780 -x (--extra) <key=value>
1781 Add a key/value pair to the 'extras' dict, which device-specific extension
1782 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001783
Doug Zongkereef39442009-04-02 12:14:19 -07001784 -v (--verbose)
1785 Show command lines being executed.
1786
1787 -h (--help)
1788 Display this usage message and exit.
1789"""
1790
1791def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001792 print(docstring.rstrip("\n"))
1793 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001794
1795
1796def ParseOptions(argv,
1797 docstring,
1798 extra_opts="", extra_long_opts=(),
1799 extra_option_handler=None):
1800 """Parse the options in argv and return any arguments that aren't
1801 flags. docstring is the calling module's docstring, to be displayed
1802 for errors and -h. extra_opts and extra_long_opts are for flags
1803 defined by the caller, which are processed by passing them to
1804 extra_option_handler."""
1805
1806 try:
1807 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001808 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001809 ["help", "verbose", "path=", "signapk_path=",
1810 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001811 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001812 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1813 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001814 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001815 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001816 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001817 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001818 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001819 sys.exit(2)
1820
Doug Zongkereef39442009-04-02 12:14:19 -07001821 for o, a in opts:
1822 if o in ("-h", "--help"):
1823 Usage(docstring)
1824 sys.exit()
1825 elif o in ("-v", "--verbose"):
1826 OPTIONS.verbose = True
1827 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001828 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001829 elif o in ("--signapk_path",):
1830 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001831 elif o in ("--signapk_shared_library_path",):
1832 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001833 elif o in ("--extra_signapk_args",):
1834 OPTIONS.extra_signapk_args = shlex.split(a)
1835 elif o in ("--java_path",):
1836 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001837 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001838 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001839 elif o in ("--public_key_suffix",):
1840 OPTIONS.public_key_suffix = a
1841 elif o in ("--private_key_suffix",):
1842 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001843 elif o in ("--boot_signer_path",):
1844 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001845 elif o in ("--boot_signer_args",):
1846 OPTIONS.boot_signer_args = shlex.split(a)
1847 elif o in ("--verity_signer_path",):
1848 OPTIONS.verity_signer_path = a
1849 elif o in ("--verity_signer_args",):
1850 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001851 elif o in ("-s", "--device_specific"):
1852 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001853 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001854 key, value = a.split("=", 1)
1855 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001856 else:
1857 if extra_option_handler is None or not extra_option_handler(o, a):
1858 assert False, "unknown option \"%s\"" % (o,)
1859
Doug Zongker85448772014-09-09 14:59:20 -07001860 if OPTIONS.search_path:
1861 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1862 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001863
1864 return args
1865
1866
Tao Bao4c851b12016-09-19 13:54:38 -07001867def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001868 """Make a temp file and add it to the list of things to be deleted
1869 when Cleanup() is called. Return the filename."""
1870 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1871 os.close(fd)
1872 OPTIONS.tempfiles.append(fn)
1873 return fn
1874
1875
Tao Bao1c830bf2017-12-25 10:43:47 -08001876def MakeTempDir(prefix='tmp', suffix=''):
1877 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1878
1879 Returns:
1880 The absolute pathname of the new directory.
1881 """
1882 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1883 OPTIONS.tempfiles.append(dir_name)
1884 return dir_name
1885
1886
Doug Zongkereef39442009-04-02 12:14:19 -07001887def Cleanup():
1888 for i in OPTIONS.tempfiles:
1889 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001890 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001891 else:
1892 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001893 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001894
1895
1896class PasswordManager(object):
1897 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001898 self.editor = os.getenv("EDITOR")
1899 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001900
1901 def GetPasswords(self, items):
1902 """Get passwords corresponding to each string in 'items',
1903 returning a dict. (The dict may have keys in addition to the
1904 values in 'items'.)
1905
1906 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1907 user edit that file to add more needed passwords. If no editor is
1908 available, or $ANDROID_PW_FILE isn't define, prompts the user
1909 interactively in the ordinary way.
1910 """
1911
1912 current = self.ReadFile()
1913
1914 first = True
1915 while True:
1916 missing = []
1917 for i in items:
1918 if i not in current or not current[i]:
1919 missing.append(i)
1920 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001921 if not missing:
1922 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001923
1924 for i in missing:
1925 current[i] = ""
1926
1927 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001928 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001929 if sys.version_info[0] >= 3:
1930 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001931 answer = raw_input("try to edit again? [y]> ").strip()
1932 if answer and answer[0] not in 'yY':
1933 raise RuntimeError("key passwords unavailable")
1934 first = False
1935
1936 current = self.UpdateAndReadFile(current)
1937
Dan Albert8b72aef2015-03-23 19:13:21 -07001938 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001939 """Prompt the user to enter a value (password) for each key in
1940 'current' whose value is fales. Returns a new dict with all the
1941 values.
1942 """
1943 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001944 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001945 if v:
1946 result[k] = v
1947 else:
1948 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001949 result[k] = getpass.getpass(
1950 "Enter password for %s key> " % k).strip()
1951 if result[k]:
1952 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001953 return result
1954
1955 def UpdateAndReadFile(self, current):
1956 if not self.editor or not self.pwfile:
1957 return self.PromptResult(current)
1958
1959 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001960 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001961 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1962 f.write("# (Additional spaces are harmless.)\n\n")
1963
1964 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001965 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001966 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001967 f.write("[[[ %s ]]] %s\n" % (v, k))
1968 if not v and first_line is None:
1969 # position cursor on first line with no password.
1970 first_line = i + 4
1971 f.close()
1972
Tao Bao986ee862018-10-04 15:46:16 -07001973 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001974
1975 return self.ReadFile()
1976
1977 def ReadFile(self):
1978 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001979 if self.pwfile is None:
1980 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001981 try:
1982 f = open(self.pwfile, "r")
1983 for line in f:
1984 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001985 if not line or line[0] == '#':
1986 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001987 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1988 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001989 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001990 else:
1991 result[m.group(2)] = m.group(1)
1992 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001993 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001994 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001995 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001996 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001997
1998
Dan Albert8e0178d2015-01-27 15:53:15 -08001999def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2000 compress_type=None):
2001 import datetime
2002
2003 # http://b/18015246
2004 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2005 # for files larger than 2GiB. We can work around this by adjusting their
2006 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2007 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2008 # it isn't clear to me exactly what circumstances cause this).
2009 # `zipfile.write()` must be used directly to work around this.
2010 #
2011 # This mess can be avoided if we port to python3.
2012 saved_zip64_limit = zipfile.ZIP64_LIMIT
2013 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2014
2015 if compress_type is None:
2016 compress_type = zip_file.compression
2017 if arcname is None:
2018 arcname = filename
2019
2020 saved_stat = os.stat(filename)
2021
2022 try:
2023 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2024 # file to be zipped and reset it when we're done.
2025 os.chmod(filename, perms)
2026
2027 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002028 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2029 # intentional. zip stores datetimes in local time without a time zone
2030 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2031 # in the zip archive.
2032 local_epoch = datetime.datetime.fromtimestamp(0)
2033 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002034 os.utime(filename, (timestamp, timestamp))
2035
2036 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2037 finally:
2038 os.chmod(filename, saved_stat.st_mode)
2039 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2040 zipfile.ZIP64_LIMIT = saved_zip64_limit
2041
2042
Tao Bao58c1b962015-05-20 09:32:18 -07002043def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002044 compress_type=None):
2045 """Wrap zipfile.writestr() function to work around the zip64 limit.
2046
2047 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2048 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2049 when calling crc32(bytes).
2050
2051 But it still works fine to write a shorter string into a large zip file.
2052 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2053 when we know the string won't be too long.
2054 """
2055
2056 saved_zip64_limit = zipfile.ZIP64_LIMIT
2057 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2058
2059 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2060 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002061 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002062 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002063 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002064 else:
Tao Baof3282b42015-04-01 11:21:55 -07002065 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002066 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2067 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2068 # such a case (since
2069 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2070 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2071 # permission bits. We follow the logic in Python 3 to get consistent
2072 # behavior between using the two versions.
2073 if not zinfo.external_attr:
2074 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002075
2076 # If compress_type is given, it overrides the value in zinfo.
2077 if compress_type is not None:
2078 zinfo.compress_type = compress_type
2079
Tao Bao58c1b962015-05-20 09:32:18 -07002080 # If perms is given, it has a priority.
2081 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002082 # If perms doesn't set the file type, mark it as a regular file.
2083 if perms & 0o770000 == 0:
2084 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002085 zinfo.external_attr = perms << 16
2086
Tao Baof3282b42015-04-01 11:21:55 -07002087 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002088 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2089
Dan Albert8b72aef2015-03-23 19:13:21 -07002090 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002091 zipfile.ZIP64_LIMIT = saved_zip64_limit
2092
2093
Tao Bao89d7ab22017-12-14 17:05:33 -08002094def ZipDelete(zip_filename, entries):
2095 """Deletes entries from a ZIP file.
2096
2097 Since deleting entries from a ZIP file is not supported, it shells out to
2098 'zip -d'.
2099
2100 Args:
2101 zip_filename: The name of the ZIP file.
2102 entries: The name of the entry, or the list of names to be deleted.
2103
2104 Raises:
2105 AssertionError: In case of non-zero return from 'zip'.
2106 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002107 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002108 entries = [entries]
2109 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002110 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002111
2112
Tao Baof3282b42015-04-01 11:21:55 -07002113def ZipClose(zip_file):
2114 # http://b/18015246
2115 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2116 # central directory.
2117 saved_zip64_limit = zipfile.ZIP64_LIMIT
2118 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2119
2120 zip_file.close()
2121
2122 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002123
2124
2125class DeviceSpecificParams(object):
2126 module = None
2127 def __init__(self, **kwargs):
2128 """Keyword arguments to the constructor become attributes of this
2129 object, which is passed to all functions in the device-specific
2130 module."""
Tao Bao38884282019-07-10 22:20:56 -07002131 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002132 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002133 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002134
2135 if self.module is None:
2136 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002137 if not path:
2138 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002139 try:
2140 if os.path.isdir(path):
2141 info = imp.find_module("releasetools", [path])
2142 else:
2143 d, f = os.path.split(path)
2144 b, x = os.path.splitext(f)
2145 if x == ".py":
2146 f = b
2147 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002148 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002149 self.module = imp.load_module("device_specific", *info)
2150 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002151 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002152
2153 def _DoCall(self, function_name, *args, **kwargs):
2154 """Call the named function in the device-specific module, passing
2155 the given args and kwargs. The first argument to the call will be
2156 the DeviceSpecific object itself. If there is no module, or the
2157 module does not define the function, return the value of the
2158 'default' kwarg (which itself defaults to None)."""
2159 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002160 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002161 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2162
2163 def FullOTA_Assertions(self):
2164 """Called after emitting the block of assertions at the top of a
2165 full OTA package. Implementations can add whatever additional
2166 assertions they like."""
2167 return self._DoCall("FullOTA_Assertions")
2168
Doug Zongkere5ff5902012-01-17 10:55:37 -08002169 def FullOTA_InstallBegin(self):
2170 """Called at the start of full OTA installation."""
2171 return self._DoCall("FullOTA_InstallBegin")
2172
Yifan Hong10c530d2018-12-27 17:34:18 -08002173 def FullOTA_GetBlockDifferences(self):
2174 """Called during full OTA installation and verification.
2175 Implementation should return a list of BlockDifference objects describing
2176 the update on each additional partitions.
2177 """
2178 return self._DoCall("FullOTA_GetBlockDifferences")
2179
Doug Zongker05d3dea2009-06-22 11:32:31 -07002180 def FullOTA_InstallEnd(self):
2181 """Called at the end of full OTA installation; typically this is
2182 used to install the image for the device's baseband processor."""
2183 return self._DoCall("FullOTA_InstallEnd")
2184
2185 def IncrementalOTA_Assertions(self):
2186 """Called after emitting the block of assertions at the top of an
2187 incremental OTA package. Implementations can add whatever
2188 additional assertions they like."""
2189 return self._DoCall("IncrementalOTA_Assertions")
2190
Doug Zongkere5ff5902012-01-17 10:55:37 -08002191 def IncrementalOTA_VerifyBegin(self):
2192 """Called at the start of the verification phase of incremental
2193 OTA installation; additional checks can be placed here to abort
2194 the script before any changes are made."""
2195 return self._DoCall("IncrementalOTA_VerifyBegin")
2196
Doug Zongker05d3dea2009-06-22 11:32:31 -07002197 def IncrementalOTA_VerifyEnd(self):
2198 """Called at the end of the verification phase of incremental OTA
2199 installation; additional checks can be placed here to abort the
2200 script before any changes are made."""
2201 return self._DoCall("IncrementalOTA_VerifyEnd")
2202
Doug Zongkere5ff5902012-01-17 10:55:37 -08002203 def IncrementalOTA_InstallBegin(self):
2204 """Called at the start of incremental OTA installation (after
2205 verification is complete)."""
2206 return self._DoCall("IncrementalOTA_InstallBegin")
2207
Yifan Hong10c530d2018-12-27 17:34:18 -08002208 def IncrementalOTA_GetBlockDifferences(self):
2209 """Called during incremental OTA installation and verification.
2210 Implementation should return a list of BlockDifference objects describing
2211 the update on each additional partitions.
2212 """
2213 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2214
Doug Zongker05d3dea2009-06-22 11:32:31 -07002215 def IncrementalOTA_InstallEnd(self):
2216 """Called at the end of incremental OTA installation; typically
2217 this is used to install the image for the device's baseband
2218 processor."""
2219 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002220
Tao Bao9bc6bb22015-11-09 16:58:28 -08002221 def VerifyOTA_Assertions(self):
2222 return self._DoCall("VerifyOTA_Assertions")
2223
Tao Bao76def242017-11-21 09:25:31 -08002224
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002225class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002226 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002227 self.name = name
2228 self.data = data
2229 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002230 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002231 self.sha1 = sha1(data).hexdigest()
2232
2233 @classmethod
2234 def FromLocalFile(cls, name, diskname):
2235 f = open(diskname, "rb")
2236 data = f.read()
2237 f.close()
2238 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002239
2240 def WriteToTemp(self):
2241 t = tempfile.NamedTemporaryFile()
2242 t.write(self.data)
2243 t.flush()
2244 return t
2245
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002246 def WriteToDir(self, d):
2247 with open(os.path.join(d, self.name), "wb") as fp:
2248 fp.write(self.data)
2249
Geremy Condra36bd3652014-02-06 19:45:10 -08002250 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002251 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002252
Tao Bao76def242017-11-21 09:25:31 -08002253
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002254DIFF_PROGRAM_BY_EXT = {
2255 ".gz" : "imgdiff",
2256 ".zip" : ["imgdiff", "-z"],
2257 ".jar" : ["imgdiff", "-z"],
2258 ".apk" : ["imgdiff", "-z"],
2259 ".img" : "imgdiff",
2260 }
2261
Tao Bao76def242017-11-21 09:25:31 -08002262
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002263class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002264 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002265 self.tf = tf
2266 self.sf = sf
2267 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002268 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002269
2270 def ComputePatch(self):
2271 """Compute the patch (as a string of data) needed to turn sf into
2272 tf. Returns the same tuple as GetPatch()."""
2273
2274 tf = self.tf
2275 sf = self.sf
2276
Doug Zongker24cd2802012-08-14 16:36:15 -07002277 if self.diff_program:
2278 diff_program = self.diff_program
2279 else:
2280 ext = os.path.splitext(tf.name)[1]
2281 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002282
2283 ttemp = tf.WriteToTemp()
2284 stemp = sf.WriteToTemp()
2285
2286 ext = os.path.splitext(tf.name)[1]
2287
2288 try:
2289 ptemp = tempfile.NamedTemporaryFile()
2290 if isinstance(diff_program, list):
2291 cmd = copy.copy(diff_program)
2292 else:
2293 cmd = [diff_program]
2294 cmd.append(stemp.name)
2295 cmd.append(ttemp.name)
2296 cmd.append(ptemp.name)
2297 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002298 err = []
2299 def run():
2300 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002301 if e:
2302 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002303 th = threading.Thread(target=run)
2304 th.start()
2305 th.join(timeout=300) # 5 mins
2306 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002307 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002308 p.terminate()
2309 th.join(5)
2310 if th.is_alive():
2311 p.kill()
2312 th.join()
2313
Tianjie Xua2a9f992018-01-05 15:15:54 -08002314 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002315 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002316 self.patch = None
2317 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002318 diff = ptemp.read()
2319 finally:
2320 ptemp.close()
2321 stemp.close()
2322 ttemp.close()
2323
2324 self.patch = diff
2325 return self.tf, self.sf, self.patch
2326
2327
2328 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002329 """Returns a tuple of (target_file, source_file, patch_data).
2330
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002331 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002332 computing the patch failed.
2333 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002334 return self.tf, self.sf, self.patch
2335
2336
2337def ComputeDifferences(diffs):
2338 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002339 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002340
2341 # Do the largest files first, to try and reduce the long-pole effect.
2342 by_size = [(i.tf.size, i) for i in diffs]
2343 by_size.sort(reverse=True)
2344 by_size = [i[1] for i in by_size]
2345
2346 lock = threading.Lock()
2347 diff_iter = iter(by_size) # accessed under lock
2348
2349 def worker():
2350 try:
2351 lock.acquire()
2352 for d in diff_iter:
2353 lock.release()
2354 start = time.time()
2355 d.ComputePatch()
2356 dur = time.time() - start
2357 lock.acquire()
2358
2359 tf, sf, patch = d.GetPatch()
2360 if sf.name == tf.name:
2361 name = tf.name
2362 else:
2363 name = "%s (%s)" % (tf.name, sf.name)
2364 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002365 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002366 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002367 logger.info(
2368 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2369 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002370 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002371 except Exception:
2372 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002373 raise
2374
2375 # start worker threads; wait for them all to finish.
2376 threads = [threading.Thread(target=worker)
2377 for i in range(OPTIONS.worker_threads)]
2378 for th in threads:
2379 th.start()
2380 while threads:
2381 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002382
2383
Dan Albert8b72aef2015-03-23 19:13:21 -07002384class BlockDifference(object):
2385 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002386 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002387 self.tgt = tgt
2388 self.src = src
2389 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002390 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002391 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002392
Tao Baodd2a5892015-03-12 12:32:37 -07002393 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002394 version = max(
2395 int(i) for i in
2396 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002397 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002398 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002399
Tianjie Xu41976c72019-07-03 13:57:01 -07002400 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2401 version=self.version,
2402 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002403 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002404 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002405 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002406 self.touched_src_ranges = b.touched_src_ranges
2407 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002408
Yifan Hong10c530d2018-12-27 17:34:18 -08002409 # On devices with dynamic partitions, for new partitions,
2410 # src is None but OPTIONS.source_info_dict is not.
2411 if OPTIONS.source_info_dict is None:
2412 is_dynamic_build = OPTIONS.info_dict.get(
2413 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002414 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002415 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002416 is_dynamic_build = OPTIONS.source_info_dict.get(
2417 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002418 is_dynamic_source = partition in shlex.split(
2419 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002420
Yifan Hongbb2658d2019-01-25 12:30:58 -08002421 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002422 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2423
Yifan Hongbb2658d2019-01-25 12:30:58 -08002424 # For dynamic partitions builds, check partition list in both source
2425 # and target build because new partitions may be added, and existing
2426 # partitions may be removed.
2427 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2428
Yifan Hong10c530d2018-12-27 17:34:18 -08002429 if is_dynamic:
2430 self.device = 'map_partition("%s")' % partition
2431 else:
2432 if OPTIONS.source_info_dict is None:
2433 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2434 else:
2435 _, device_path = GetTypeAndDevice("/" + partition,
2436 OPTIONS.source_info_dict)
2437 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002438
Tao Baod8d14be2016-02-04 14:26:02 -08002439 @property
2440 def required_cache(self):
2441 return self._required_cache
2442
Tao Bao76def242017-11-21 09:25:31 -08002443 def WriteScript(self, script, output_zip, progress=None,
2444 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002445 if not self.src:
2446 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002447 script.Print("Patching %s image unconditionally..." % (self.partition,))
2448 else:
2449 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002450
Dan Albert8b72aef2015-03-23 19:13:21 -07002451 if progress:
2452 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002453 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002454
2455 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002456 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002457
Tao Bao9bc6bb22015-11-09 16:58:28 -08002458 def WriteStrictVerifyScript(self, script):
2459 """Verify all the blocks in the care_map, including clobbered blocks.
2460
2461 This differs from the WriteVerifyScript() function: a) it prints different
2462 error messages; b) it doesn't allow half-way updated images to pass the
2463 verification."""
2464
2465 partition = self.partition
2466 script.Print("Verifying %s..." % (partition,))
2467 ranges = self.tgt.care_map
2468 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002469 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002470 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2471 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002472 self.device, ranges_str,
2473 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002474 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002475 script.AppendExtra("")
2476
Tao Baod522bdc2016-04-12 15:53:16 -07002477 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002478 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002479
2480 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002481 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002482 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002483
2484 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002485 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002486 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002487 ranges = self.touched_src_ranges
2488 expected_sha1 = self.touched_src_sha1
2489 else:
2490 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2491 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002492
2493 # No blocks to be checked, skipping.
2494 if not ranges:
2495 return
2496
Tao Bao5ece99d2015-05-12 11:42:31 -07002497 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002498 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002499 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002500 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2501 '"%s.patch.dat")) then' % (
2502 self.device, ranges_str, expected_sha1,
2503 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002504 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002505 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002506
Tianjie Xufc3422a2015-12-15 11:53:59 -08002507 if self.version >= 4:
2508
2509 # Bug: 21124327
2510 # When generating incrementals for the system and vendor partitions in
2511 # version 4 or newer, explicitly check the first block (which contains
2512 # the superblock) of the partition to see if it's what we expect. If
2513 # this check fails, give an explicit log message about the partition
2514 # having been remounted R/W (the most likely explanation).
2515 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002516 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002517
2518 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002519 if partition == "system":
2520 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2521 else:
2522 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002523 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002524 'ifelse (block_image_recover({device}, "{ranges}") && '
2525 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002526 'package_extract_file("{partition}.transfer.list"), '
2527 '"{partition}.new.dat", "{partition}.patch.dat"), '
2528 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002529 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002530 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002531 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002532
Tao Baodd2a5892015-03-12 12:32:37 -07002533 # Abort the OTA update. Note that the incremental OTA cannot be applied
2534 # even if it may match the checksum of the target partition.
2535 # a) If version < 3, operations like move and erase will make changes
2536 # unconditionally and damage the partition.
2537 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002538 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002539 if partition == "system":
2540 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2541 else:
2542 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2543 script.AppendExtra((
2544 'abort("E%d: %s partition has unexpected contents");\n'
2545 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002546
Yifan Hong10c530d2018-12-27 17:34:18 -08002547 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002548 partition = self.partition
2549 script.Print('Verifying the updated %s image...' % (partition,))
2550 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2551 ranges = self.tgt.care_map
2552 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002553 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002554 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002555 self.device, ranges_str,
2556 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002557
2558 # Bug: 20881595
2559 # Verify that extended blocks are really zeroed out.
2560 if self.tgt.extended:
2561 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002562 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002563 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002564 self.device, ranges_str,
2565 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002566 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002567 if partition == "system":
2568 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2569 else:
2570 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002571 script.AppendExtra(
2572 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002573 ' abort("E%d: %s partition has unexpected non-zero contents after '
2574 'OTA update");\n'
2575 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002576 else:
2577 script.Print('Verified the updated %s image.' % (partition,))
2578
Tianjie Xu209db462016-05-24 17:34:52 -07002579 if partition == "system":
2580 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2581 else:
2582 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2583
Tao Bao5fcaaef2015-06-01 13:40:49 -07002584 script.AppendExtra(
2585 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002586 ' abort("E%d: %s partition has unexpected contents after OTA '
2587 'update");\n'
2588 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002589
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002590 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002591 ZipWrite(output_zip,
2592 '{}.transfer.list'.format(self.path),
2593 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002594
Tao Bao76def242017-11-21 09:25:31 -08002595 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2596 # its size. Quailty 9 almost triples the compression time but doesn't
2597 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002598 # zip | brotli(quality 6) | brotli(quality 9)
2599 # compressed_size: 942M | 869M (~8% reduced) | 854M
2600 # compression_time: 75s | 265s | 719s
2601 # decompression_time: 15s | 25s | 25s
2602
2603 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002604 brotli_cmd = ['brotli', '--quality=6',
2605 '--output={}.new.dat.br'.format(self.path),
2606 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002607 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002608 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002609
2610 new_data_name = '{}.new.dat.br'.format(self.partition)
2611 ZipWrite(output_zip,
2612 '{}.new.dat.br'.format(self.path),
2613 new_data_name,
2614 compress_type=zipfile.ZIP_STORED)
2615 else:
2616 new_data_name = '{}.new.dat'.format(self.partition)
2617 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2618
Dan Albert8e0178d2015-01-27 15:53:15 -08002619 ZipWrite(output_zip,
2620 '{}.patch.dat'.format(self.path),
2621 '{}.patch.dat'.format(self.partition),
2622 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002623
Tianjie Xu209db462016-05-24 17:34:52 -07002624 if self.partition == "system":
2625 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2626 else:
2627 code = ErrorCode.VENDOR_UPDATE_FAILURE
2628
Yifan Hong10c530d2018-12-27 17:34:18 -08002629 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002630 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002631 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002632 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002633 device=self.device, partition=self.partition,
2634 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002635 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002636
Dan Albert8b72aef2015-03-23 19:13:21 -07002637 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002638 data = source.ReadRangeSet(ranges)
2639 ctx = sha1()
2640
2641 for p in data:
2642 ctx.update(p)
2643
2644 return ctx.hexdigest()
2645
Tao Baoe9b61912015-07-09 17:37:49 -07002646 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2647 """Return the hash value for all zero blocks."""
2648 zero_block = '\x00' * 4096
2649 ctx = sha1()
2650 for _ in range(num_blocks):
2651 ctx.update(zero_block)
2652
2653 return ctx.hexdigest()
2654
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002655
Tianjie Xu41976c72019-07-03 13:57:01 -07002656# Expose these two classes to support vendor-specific scripts
2657DataImage = images.DataImage
2658EmptyImage = images.EmptyImage
2659
Tao Bao76def242017-11-21 09:25:31 -08002660
Doug Zongker96a57e72010-09-26 14:57:41 -07002661# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002662PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002663 "ext4": "EMMC",
2664 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002665 "f2fs": "EMMC",
2666 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002667}
Doug Zongker96a57e72010-09-26 14:57:41 -07002668
Tao Bao76def242017-11-21 09:25:31 -08002669
Doug Zongker96a57e72010-09-26 14:57:41 -07002670def GetTypeAndDevice(mount_point, info):
2671 fstab = info["fstab"]
2672 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002673 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2674 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002675 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002676 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002677
2678
2679def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002680 """Parses and converts a PEM-encoded certificate into DER-encoded.
2681
2682 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2683
2684 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002685 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002686 """
2687 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002688 save = False
2689 for line in data.split("\n"):
2690 if "--END CERTIFICATE--" in line:
2691 break
2692 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002693 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002694 if "--BEGIN CERTIFICATE--" in line:
2695 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002696 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002697 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002698
Tao Bao04e1f012018-02-04 12:13:35 -08002699
2700def ExtractPublicKey(cert):
2701 """Extracts the public key (PEM-encoded) from the given certificate file.
2702
2703 Args:
2704 cert: The certificate filename.
2705
2706 Returns:
2707 The public key string.
2708
2709 Raises:
2710 AssertionError: On non-zero return from 'openssl'.
2711 """
2712 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2713 # While openssl 1.1 writes the key into the given filename followed by '-out',
2714 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2715 # stdout instead.
2716 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2717 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2718 pubkey, stderrdata = proc.communicate()
2719 assert proc.returncode == 0, \
2720 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2721 return pubkey
2722
2723
Tao Bao1ac886e2019-06-26 11:58:22 -07002724def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002725 """Extracts the AVB public key from the given public or private key.
2726
2727 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002728 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002729 key: The input key file, which should be PEM-encoded public or private key.
2730
2731 Returns:
2732 The path to the extracted AVB public key file.
2733 """
2734 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2735 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002736 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002737 return output
2738
2739
Doug Zongker412c02f2014-02-13 10:58:24 -08002740def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2741 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002742 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002743
Tao Bao6d5d6232018-03-09 17:04:42 -08002744 Most of the space in the boot and recovery images is just the kernel, which is
2745 identical for the two, so the resulting patch should be efficient. Add it to
2746 the output zip, along with a shell script that is run from init.rc on first
2747 boot to actually do the patching and install the new recovery image.
2748
2749 Args:
2750 input_dir: The top-level input directory of the target-files.zip.
2751 output_sink: The callback function that writes the result.
2752 recovery_img: File object for the recovery image.
2753 boot_img: File objects for the boot image.
2754 info_dict: A dict returned by common.LoadInfoDict() on the input
2755 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002756 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002757 if info_dict is None:
2758 info_dict = OPTIONS.info_dict
2759
Tao Bao6d5d6232018-03-09 17:04:42 -08002760 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002761 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2762
2763 if board_uses_vendorimage:
2764 # In this case, the output sink is rooted at VENDOR
2765 recovery_img_path = "etc/recovery.img"
2766 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2767 sh_dir = "bin"
2768 else:
2769 # In this case the output sink is rooted at SYSTEM
2770 recovery_img_path = "vendor/etc/recovery.img"
2771 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2772 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002773
Tao Baof2cffbd2015-07-22 12:33:18 -07002774 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002775 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002776
2777 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002778 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002779 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002780 # With system-root-image, boot and recovery images will have mismatching
2781 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2782 # to handle such a case.
2783 if system_root_image:
2784 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002785 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002786 assert not os.path.exists(path)
2787 else:
2788 diff_program = ["imgdiff"]
2789 if os.path.exists(path):
2790 diff_program.append("-b")
2791 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002792 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002793 else:
2794 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002795
2796 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2797 _, _, patch = d.ComputePatch()
2798 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002799
Dan Albertebb19aa2015-03-27 19:11:53 -07002800 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002801 # The following GetTypeAndDevice()s need to use the path in the target
2802 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002803 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2804 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2805 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002806 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002807
Tao Baof2cffbd2015-07-22 12:33:18 -07002808 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002809
2810 # Note that we use /vendor to refer to the recovery resources. This will
2811 # work for a separate vendor partition mounted at /vendor or a
2812 # /system/vendor subdirectory on the system partition, for which init will
2813 # create a symlink from /vendor to /system/vendor.
2814
2815 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002816if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2817 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002818 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002819 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2820 log -t recovery "Installing new recovery image: succeeded" || \\
2821 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002822else
2823 log -t recovery "Recovery image already installed"
2824fi
2825""" % {'type': recovery_type,
2826 'device': recovery_device,
2827 'sha1': recovery_img.sha1,
2828 'size': recovery_img.size}
2829 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002830 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002831if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2832 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002833 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002834 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2835 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2836 log -t recovery "Installing new recovery image: succeeded" || \\
2837 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002838else
2839 log -t recovery "Recovery image already installed"
2840fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002841""" % {'boot_size': boot_img.size,
2842 'boot_sha1': boot_img.sha1,
2843 'recovery_size': recovery_img.size,
2844 'recovery_sha1': recovery_img.sha1,
2845 'boot_type': boot_type,
2846 'boot_device': boot_device,
2847 'recovery_type': recovery_type,
2848 'recovery_device': recovery_device,
2849 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002850
Bill Peckhame868aec2019-09-17 17:06:47 -07002851 # The install script location moved from /system/etc to /system/bin in the L
2852 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2853 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002854
Tao Bao32fcdab2018-10-12 10:30:39 -07002855 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002856
Tao Baoda30cfa2017-12-01 16:19:46 -08002857 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002858
2859
2860class DynamicPartitionUpdate(object):
2861 def __init__(self, src_group=None, tgt_group=None, progress=None,
2862 block_difference=None):
2863 self.src_group = src_group
2864 self.tgt_group = tgt_group
2865 self.progress = progress
2866 self.block_difference = block_difference
2867
2868 @property
2869 def src_size(self):
2870 if not self.block_difference:
2871 return 0
2872 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2873
2874 @property
2875 def tgt_size(self):
2876 if not self.block_difference:
2877 return 0
2878 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2879
2880 @staticmethod
2881 def _GetSparseImageSize(img):
2882 if not img:
2883 return 0
2884 return img.blocksize * img.total_blocks
2885
2886
2887class DynamicGroupUpdate(object):
2888 def __init__(self, src_size=None, tgt_size=None):
2889 # None: group does not exist. 0: no size limits.
2890 self.src_size = src_size
2891 self.tgt_size = tgt_size
2892
2893
2894class DynamicPartitionsDifference(object):
2895 def __init__(self, info_dict, block_diffs, progress_dict=None,
2896 source_info_dict=None):
2897 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002898 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002899
2900 self._remove_all_before_apply = False
2901 if source_info_dict is None:
2902 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002903 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002904
Tao Baof1113e92019-06-18 12:10:14 -07002905 block_diff_dict = collections.OrderedDict(
2906 [(e.partition, e) for e in block_diffs])
2907
Yifan Hong10c530d2018-12-27 17:34:18 -08002908 assert len(block_diff_dict) == len(block_diffs), \
2909 "Duplicated BlockDifference object for {}".format(
2910 [partition for partition, count in
2911 collections.Counter(e.partition for e in block_diffs).items()
2912 if count > 1])
2913
Yifan Hong79997e52019-01-23 16:56:19 -08002914 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002915
2916 for p, block_diff in block_diff_dict.items():
2917 self._partition_updates[p] = DynamicPartitionUpdate()
2918 self._partition_updates[p].block_difference = block_diff
2919
2920 for p, progress in progress_dict.items():
2921 if p in self._partition_updates:
2922 self._partition_updates[p].progress = progress
2923
2924 tgt_groups = shlex.split(info_dict.get(
2925 "super_partition_groups", "").strip())
2926 src_groups = shlex.split(source_info_dict.get(
2927 "super_partition_groups", "").strip())
2928
2929 for g in tgt_groups:
2930 for p in shlex.split(info_dict.get(
2931 "super_%s_partition_list" % g, "").strip()):
2932 assert p in self._partition_updates, \
2933 "{} is in target super_{}_partition_list but no BlockDifference " \
2934 "object is provided.".format(p, g)
2935 self._partition_updates[p].tgt_group = g
2936
2937 for g in src_groups:
2938 for p in shlex.split(source_info_dict.get(
2939 "super_%s_partition_list" % g, "").strip()):
2940 assert p in self._partition_updates, \
2941 "{} is in source super_{}_partition_list but no BlockDifference " \
2942 "object is provided.".format(p, g)
2943 self._partition_updates[p].src_group = g
2944
Yifan Hong45433e42019-01-18 13:55:25 -08002945 target_dynamic_partitions = set(shlex.split(info_dict.get(
2946 "dynamic_partition_list", "").strip()))
2947 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2948 if u.tgt_size)
2949 assert block_diffs_with_target == target_dynamic_partitions, \
2950 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2951 list(target_dynamic_partitions), list(block_diffs_with_target))
2952
2953 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2954 "dynamic_partition_list", "").strip()))
2955 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2956 if u.src_size)
2957 assert block_diffs_with_source == source_dynamic_partitions, \
2958 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2959 list(source_dynamic_partitions), list(block_diffs_with_source))
2960
Yifan Hong10c530d2018-12-27 17:34:18 -08002961 if self._partition_updates:
2962 logger.info("Updating dynamic partitions %s",
2963 self._partition_updates.keys())
2964
Yifan Hong79997e52019-01-23 16:56:19 -08002965 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002966
2967 for g in tgt_groups:
2968 self._group_updates[g] = DynamicGroupUpdate()
2969 self._group_updates[g].tgt_size = int(info_dict.get(
2970 "super_%s_group_size" % g, "0").strip())
2971
2972 for g in src_groups:
2973 if g not in self._group_updates:
2974 self._group_updates[g] = DynamicGroupUpdate()
2975 self._group_updates[g].src_size = int(source_info_dict.get(
2976 "super_%s_group_size" % g, "0").strip())
2977
2978 self._Compute()
2979
2980 def WriteScript(self, script, output_zip, write_verify_script=False):
2981 script.Comment('--- Start patching dynamic partitions ---')
2982 for p, u in self._partition_updates.items():
2983 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2984 script.Comment('Patch partition %s' % p)
2985 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2986 write_verify_script=False)
2987
2988 op_list_path = MakeTempFile()
2989 with open(op_list_path, 'w') as f:
2990 for line in self._op_list:
2991 f.write('{}\n'.format(line))
2992
2993 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2994
2995 script.Comment('Update dynamic partition metadata')
2996 script.AppendExtra('assert(update_dynamic_partitions('
2997 'package_extract_file("dynamic_partitions_op_list")));')
2998
2999 if write_verify_script:
3000 for p, u in self._partition_updates.items():
3001 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3002 u.block_difference.WritePostInstallVerifyScript(script)
3003 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3004
3005 for p, u in self._partition_updates.items():
3006 if u.tgt_size and u.src_size <= u.tgt_size:
3007 script.Comment('Patch partition %s' % p)
3008 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3009 write_verify_script=write_verify_script)
3010 if write_verify_script:
3011 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3012
3013 script.Comment('--- End patching dynamic partitions ---')
3014
3015 def _Compute(self):
3016 self._op_list = list()
3017
3018 def append(line):
3019 self._op_list.append(line)
3020
3021 def comment(line):
3022 self._op_list.append("# %s" % line)
3023
3024 if self._remove_all_before_apply:
3025 comment('Remove all existing dynamic partitions and groups before '
3026 'applying full OTA')
3027 append('remove_all_groups')
3028
3029 for p, u in self._partition_updates.items():
3030 if u.src_group and not u.tgt_group:
3031 append('remove %s' % p)
3032
3033 for p, u in self._partition_updates.items():
3034 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3035 comment('Move partition %s from %s to default' % (p, u.src_group))
3036 append('move %s default' % p)
3037
3038 for p, u in self._partition_updates.items():
3039 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3040 comment('Shrink partition %s from %d to %d' %
3041 (p, u.src_size, u.tgt_size))
3042 append('resize %s %s' % (p, u.tgt_size))
3043
3044 for g, u in self._group_updates.items():
3045 if u.src_size is not None and u.tgt_size is None:
3046 append('remove_group %s' % g)
3047 if (u.src_size is not None and u.tgt_size is not None and
3048 u.src_size > u.tgt_size):
3049 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3050 append('resize_group %s %d' % (g, u.tgt_size))
3051
3052 for g, u in self._group_updates.items():
3053 if u.src_size is None and u.tgt_size is not None:
3054 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3055 append('add_group %s %d' % (g, u.tgt_size))
3056 if (u.src_size is not None and u.tgt_size is not None and
3057 u.src_size < u.tgt_size):
3058 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3059 append('resize_group %s %d' % (g, u.tgt_size))
3060
3061 for p, u in self._partition_updates.items():
3062 if u.tgt_group and not u.src_group:
3063 comment('Add partition %s to group %s' % (p, u.tgt_group))
3064 append('add %s %s' % (p, u.tgt_group))
3065
3066 for p, u in self._partition_updates.items():
3067 if u.tgt_size and u.src_size < u.tgt_size:
3068 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3069 append('resize %s %d' % (p, u.tgt_size))
3070
3071 for p, u in self._partition_updates.items():
3072 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3073 comment('Move partition %s from default to %s' %
3074 (p, u.tgt_group))
3075 append('move %s %s' % (p, u.tgt_group))