blob: 6a6f11989736b9f375122812731195dc207cbca1 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Tao Baoda30cfa2017-12-01 16:19:46 -080017import base64
Yifan Hong10c530d2018-12-27 17:34:18 -080018import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070019import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070020import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070021import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070022import getopt
23import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010024import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070025import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070026import json
27import logging
28import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070029import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080030import platform
Doug Zongkereef39442009-04-02 12:14:19 -070031import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070032import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070033import shutil
34import subprocess
35import sys
36import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070037import threading
38import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070039import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080040from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070041
Tianjie Xu41976c72019-07-03 13:57:01 -070042import images
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Tianjie Xu41976c72019-07-03 13:57:01 -070044from blockimgdiff import BlockImageDiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070045
Tao Bao32fcdab2018-10-12 10:30:39 -070046logger = logging.getLogger(__name__)
47
Tao Bao986ee862018-10-04 15:46:16 -070048
Dan Albert8b72aef2015-03-23 19:13:21 -070049class Options(object):
Tao Baoafd92a82019-10-10 22:44:22 -070050
Dan Albert8b72aef2015-03-23 19:13:21 -070051 def __init__(self):
Tao Baoafd92a82019-10-10 22:44:22 -070052 # Set up search path, in order to find framework/ and lib64/. At the time of
53 # running this function, user-supplied search path (`--path`) hasn't been
54 # available. So the value set here is the default, which might be overridden
55 # by commandline flag later.
56 exec_path = sys.argv[0]
57 if exec_path.endswith('.py'):
58 script_name = os.path.basename(exec_path)
59 # logger hasn't been initialized yet at this point. Use print to output
60 # warnings.
61 print(
62 'Warning: releasetools script should be invoked as hermetic Python '
63 'executable -- build and run `{}` directly.'.format(script_name[:-3]),
64 file=sys.stderr)
Robin Lee34ea7392020-01-02 20:21:18 +010065 self.search_path = os.path.realpath(os.path.join(os.path.dirname(exec_path), '..'))
Pavel Salomatov32676552019-03-06 20:00:45 +030066
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080068 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070069 self.extra_signapk_args = []
70 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080071 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070072 self.public_key_suffix = ".x509.pem"
73 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070074 # use otatools built boot_signer by default
75 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070076 self.boot_signer_args = []
77 self.verity_signer_path = None
78 self.verity_signer_args = []
Dan Austin52903642019-12-12 15:44:00 -080079 self.aftl_server = None
80 self.aftl_key_path = None
81 self.aftl_manufacturer_key_path = None
82 self.aftl_signer_helper = None
Dan Albert8b72aef2015-03-23 19:13:21 -070083 self.verbose = False
84 self.tempfiles = []
85 self.device_specific = None
86 self.extras = {}
87 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070088 self.source_info_dict = None
89 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070090 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070091 # Stash size cannot exceed cache_size * threshold.
92 self.cache_size = None
93 self.stash_threshold = 0.8
Yifan Hong30910932019-10-25 20:36:55 -070094 self.logfile = None
Dan Albert8b72aef2015-03-23 19:13:21 -070095
96
97OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070098
Tao Bao71197512018-10-11 14:08:45 -070099# The block size that's used across the releasetools scripts.
100BLOCK_SIZE = 4096
101
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800102# Values for "certificate" in apkcerts that mean special things.
103SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
104
Tao Bao5cc0abb2019-03-21 10:18:05 -0700105# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
106# that system_other is not in the list because we don't want to include its
107# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900108AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
Steve Mucklee1b10862019-07-10 10:49:37 -0700109 'system_ext', 'vendor', 'vendor_boot')
Tao Bao9dd909e2017-11-14 11:27:32 -0800110
Tao Bao08c190f2019-06-03 23:07:58 -0700111# Chained VBMeta partitions.
112AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
113
Tianjie Xu861f4132018-09-12 11:49:33 -0700114# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900115PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700116
117
Tianjie Xu209db462016-05-24 17:34:52 -0700118class ErrorCode(object):
119 """Define error_codes for failures that happen during the actual
120 update package installation.
121
122 Error codes 0-999 are reserved for failures before the package
123 installation (i.e. low battery, package verification failure).
124 Detailed code in 'bootable/recovery/error_code.h' """
125
126 SYSTEM_VERIFICATION_FAILURE = 1000
127 SYSTEM_UPDATE_FAILURE = 1001
128 SYSTEM_UNEXPECTED_CONTENTS = 1002
129 SYSTEM_NONZERO_CONTENTS = 1003
130 SYSTEM_RECOVER_FAILURE = 1004
131 VENDOR_VERIFICATION_FAILURE = 2000
132 VENDOR_UPDATE_FAILURE = 2001
133 VENDOR_UNEXPECTED_CONTENTS = 2002
134 VENDOR_NONZERO_CONTENTS = 2003
135 VENDOR_RECOVER_FAILURE = 2004
136 OEM_PROP_MISMATCH = 3000
137 FINGERPRINT_MISMATCH = 3001
138 THUMBPRINT_MISMATCH = 3002
139 OLDER_BUILD = 3003
140 DEVICE_MISMATCH = 3004
141 BAD_PATCH_FILE = 3005
142 INSUFFICIENT_CACHE_SPACE = 3006
143 TUNE_PARTITION_FAILURE = 3007
144 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800145
Tao Bao80921982018-03-21 21:02:19 -0700146
Dan Albert8b72aef2015-03-23 19:13:21 -0700147class ExternalError(RuntimeError):
148 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700149
150
Tao Bao32fcdab2018-10-12 10:30:39 -0700151def InitLogging():
152 DEFAULT_LOGGING_CONFIG = {
153 'version': 1,
154 'disable_existing_loggers': False,
155 'formatters': {
156 'standard': {
157 'format':
158 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
159 'datefmt': '%Y-%m-%d %H:%M:%S',
160 },
161 },
162 'handlers': {
163 'default': {
164 'class': 'logging.StreamHandler',
165 'formatter': 'standard',
Yifan Hong30910932019-10-25 20:36:55 -0700166 'level': 'WARNING',
Tao Bao32fcdab2018-10-12 10:30:39 -0700167 },
168 },
169 'loggers': {
170 '': {
171 'handlers': ['default'],
Tao Bao32fcdab2018-10-12 10:30:39 -0700172 'propagate': True,
Yifan Hong30910932019-10-25 20:36:55 -0700173 'level': 'INFO',
Tao Bao32fcdab2018-10-12 10:30:39 -0700174 }
175 }
176 }
177 env_config = os.getenv('LOGGING_CONFIG')
178 if env_config:
179 with open(env_config) as f:
180 config = json.load(f)
181 else:
182 config = DEFAULT_LOGGING_CONFIG
183
184 # Increase the logging level for verbose mode.
185 if OPTIONS.verbose:
Yifan Hong30910932019-10-25 20:36:55 -0700186 config = copy.deepcopy(config)
187 config['handlers']['default']['level'] = 'INFO'
188
189 if OPTIONS.logfile:
190 config = copy.deepcopy(config)
191 config['handlers']['logfile'] = {
192 'class': 'logging.FileHandler',
193 'formatter': 'standard',
194 'level': 'INFO',
195 'mode': 'w',
196 'filename': OPTIONS.logfile,
197 }
198 config['loggers']['']['handlers'].append('logfile')
Tao Bao32fcdab2018-10-12 10:30:39 -0700199
200 logging.config.dictConfig(config)
201
202
Tao Bao39451582017-05-04 11:10:47 -0700203def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700204 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700205
Tao Bao73dd4f42018-10-04 16:25:33 -0700206 Args:
207 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700208 verbose: Whether the commands should be shown. Default to the global
209 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700210 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
211 stdin, etc. stdout and stderr will default to subprocess.PIPE and
212 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800213 universal_newlines will default to True, as most of the users in
214 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700215
216 Returns:
217 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700218 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700219 if 'stdout' not in kwargs and 'stderr' not in kwargs:
220 kwargs['stdout'] = subprocess.PIPE
221 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800222 if 'universal_newlines' not in kwargs:
223 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700224 # Don't log any if caller explicitly says so.
225 if verbose != False:
226 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700227 return subprocess.Popen(args, **kwargs)
228
229
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800230def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800231 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800232
233 Args:
234 args: The command represented as a list of strings.
235 verbose: Whether the commands should be shown. Default to the global
236 verbosity if unspecified.
237 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
238 stdin, etc. stdout and stderr will default to subprocess.PIPE and
239 subprocess.STDOUT respectively unless caller specifies any of them.
240
Bill Peckham889b0c62019-02-21 18:53:37 -0800241 Raises:
242 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800243 """
244 proc = Run(args, verbose=verbose, **kwargs)
245 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800246
247 if proc.returncode != 0:
248 raise ExternalError(
249 "Failed to run command '{}' (exit code {})".format(
250 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800251
252
Tao Bao986ee862018-10-04 15:46:16 -0700253def RunAndCheckOutput(args, verbose=None, **kwargs):
254 """Runs the given command and returns the output.
255
256 Args:
257 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700258 verbose: Whether the commands should be shown. Default to the global
259 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700260 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
261 stdin, etc. stdout and stderr will default to subprocess.PIPE and
262 subprocess.STDOUT respectively unless caller specifies any of them.
263
264 Returns:
265 The output string.
266
267 Raises:
268 ExternalError: On non-zero exit from the command.
269 """
Tao Bao986ee862018-10-04 15:46:16 -0700270 proc = Run(args, verbose=verbose, **kwargs)
271 output, _ = proc.communicate()
Regnier, Philippe2f7e11e2019-05-22 10:10:57 +0800272 if output is None:
273 output = ""
Tao Bao32fcdab2018-10-12 10:30:39 -0700274 # Don't log any if caller explicitly says so.
275 if verbose != False:
276 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700277 if proc.returncode != 0:
278 raise ExternalError(
279 "Failed to run command '{}' (exit code {}):\n{}".format(
280 args, proc.returncode, output))
281 return output
282
283
Tao Baoc765cca2018-01-31 17:32:40 -0800284def RoundUpTo4K(value):
285 rounded_up = value + 4095
286 return rounded_up - (rounded_up % 4096)
287
288
Ying Wang7e6d4e42010-12-13 16:25:36 -0800289def CloseInheritedPipes():
290 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
291 before doing other work."""
292 if platform.system() != "Darwin":
293 return
294 for d in range(3, 1025):
295 try:
296 stat = os.fstat(d)
297 if stat is not None:
298 pipebit = stat[0] & 0x1000
299 if pipebit != 0:
300 os.close(d)
301 except OSError:
302 pass
303
304
Tao Bao1c320f82019-10-04 23:25:12 -0700305class BuildInfo(object):
306 """A class that holds the information for a given build.
307
308 This class wraps up the property querying for a given source or target build.
309 It abstracts away the logic of handling OEM-specific properties, and caches
310 the commonly used properties such as fingerprint.
311
312 There are two types of info dicts: a) build-time info dict, which is generated
313 at build time (i.e. included in a target_files zip); b) OEM info dict that is
314 specified at package generation time (via command line argument
315 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
316 having "oem_fingerprint_properties" in build-time info dict), all the queries
317 would be answered based on build-time info dict only. Otherwise if using
318 OEM-specific properties, some of them will be calculated from two info dicts.
319
320 Users can query properties similarly as using a dict() (e.g. info['fstab']),
321 or to query build properties via GetBuildProp() or GetVendorBuildProp().
322
323 Attributes:
324 info_dict: The build-time info dict.
325 is_ab: Whether it's a build that uses A/B OTA.
326 oem_dicts: A list of OEM dicts.
327 oem_props: A list of OEM properties that should be read from OEM dicts; None
328 if the build doesn't use any OEM-specific property.
329 fingerprint: The fingerprint of the build, which would be calculated based
330 on OEM properties if applicable.
331 device: The device name, which could come from OEM dicts if applicable.
332 """
333
334 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
335 "ro.product.manufacturer", "ro.product.model",
336 "ro.product.name"]
337 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
338 "system_ext", "system"]
339
Tao Bao3ed35d32019-10-07 20:48:48 -0700340 def __init__(self, info_dict, oem_dicts=None):
Tao Bao1c320f82019-10-04 23:25:12 -0700341 """Initializes a BuildInfo instance with the given dicts.
342
343 Note that it only wraps up the given dicts, without making copies.
344
345 Arguments:
346 info_dict: The build-time info dict.
347 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
348 that it always uses the first dict to calculate the fingerprint or the
349 device name. The rest would be used for asserting OEM properties only
350 (e.g. one package can be installed on one of these devices).
351
352 Raises:
353 ValueError: On invalid inputs.
354 """
355 self.info_dict = info_dict
356 self.oem_dicts = oem_dicts
357
358 self._is_ab = info_dict.get("ab_update") == "true"
359 self._oem_props = info_dict.get("oem_fingerprint_properties")
360
361 if self._oem_props:
362 assert oem_dicts, "OEM source required for this build"
363
364 # These two should be computed only after setting self._oem_props.
365 self._device = self.GetOemProperty("ro.product.device")
366 self._fingerprint = self.CalculateFingerprint()
367
368 # Sanity check the build fingerprint.
369 if (' ' in self._fingerprint or
370 any(ord(ch) > 127 for ch in self._fingerprint)):
371 raise ValueError(
372 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
373 '3.2.2. Build Parameters.'.format(self._fingerprint))
374
375 @property
376 def is_ab(self):
377 return self._is_ab
378
379 @property
380 def device(self):
381 return self._device
382
383 @property
384 def fingerprint(self):
385 return self._fingerprint
386
387 @property
388 def vendor_fingerprint(self):
389 return self._fingerprint_of("vendor")
390
391 @property
392 def product_fingerprint(self):
393 return self._fingerprint_of("product")
394
395 @property
396 def odm_fingerprint(self):
397 return self._fingerprint_of("odm")
398
399 def _fingerprint_of(self, partition):
400 if partition + ".build.prop" not in self.info_dict:
401 return None
402 build_prop = self.info_dict[partition + ".build.prop"]
403 if "ro." + partition + ".build.fingerprint" in build_prop:
404 return build_prop["ro." + partition + ".build.fingerprint"]
405 if "ro." + partition + ".build.thumbprint" in build_prop:
406 return build_prop["ro." + partition + ".build.thumbprint"]
407 return None
408
409 @property
410 def oem_props(self):
411 return self._oem_props
412
413 def __getitem__(self, key):
414 return self.info_dict[key]
415
416 def __setitem__(self, key, value):
417 self.info_dict[key] = value
418
419 def get(self, key, default=None):
420 return self.info_dict.get(key, default)
421
422 def items(self):
423 return self.info_dict.items()
424
425 def GetBuildProp(self, prop):
426 """Returns the inquired build property."""
427 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
428 return self._ResolveRoProductBuildProp(prop)
429
430 try:
431 return self.info_dict.get("build.prop", {})[prop]
432 except KeyError:
433 raise ExternalError("couldn't find %s in build.prop" % (prop,))
434
435 def _ResolveRoProductBuildProp(self, prop):
436 """Resolves the inquired ro.product.* build property"""
437 prop_val = self.info_dict.get("build.prop", {}).get(prop)
438 if prop_val:
439 return prop_val
440
441 source_order_val = self.info_dict.get("build.prop", {}).get(
442 "ro.product.property_source_order")
443 if source_order_val:
444 source_order = source_order_val.split(",")
445 else:
446 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
447
448 # Check that all sources in ro.product.property_source_order are valid
449 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
450 for x in source_order]):
451 raise ExternalError(
452 "Invalid ro.product.property_source_order '{}'".format(source_order))
453
454 for source in source_order:
455 source_prop = prop.replace(
456 "ro.product", "ro.product.{}".format(source), 1)
457 prop_val = self.info_dict.get(
458 "{}.build.prop".format(source), {}).get(source_prop)
459 if prop_val:
460 return prop_val
461
462 raise ExternalError("couldn't resolve {}".format(prop))
463
464 def GetVendorBuildProp(self, prop):
465 """Returns the inquired vendor build property."""
466 try:
467 return self.info_dict.get("vendor.build.prop", {})[prop]
468 except KeyError:
469 raise ExternalError(
470 "couldn't find %s in vendor.build.prop" % (prop,))
471
472 def GetOemProperty(self, key):
473 if self.oem_props is not None and key in self.oem_props:
474 return self.oem_dicts[0][key]
475 return self.GetBuildProp(key)
476
477 def CalculateFingerprint(self):
478 if self.oem_props is None:
479 try:
480 return self.GetBuildProp("ro.build.fingerprint")
481 except ExternalError:
482 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
483 self.GetBuildProp("ro.product.brand"),
484 self.GetBuildProp("ro.product.name"),
485 self.GetBuildProp("ro.product.device"),
486 self.GetBuildProp("ro.build.version.release"),
487 self.GetBuildProp("ro.build.id"),
488 self.GetBuildProp("ro.build.version.incremental"),
489 self.GetBuildProp("ro.build.type"),
490 self.GetBuildProp("ro.build.tags"))
491 return "%s/%s/%s:%s" % (
492 self.GetOemProperty("ro.product.brand"),
493 self.GetOemProperty("ro.product.name"),
494 self.GetOemProperty("ro.product.device"),
495 self.GetBuildProp("ro.build.thumbprint"))
496
497 def WriteMountOemScript(self, script):
498 assert self.oem_props is not None
499 recovery_mount_options = self.info_dict.get("recovery_mount_options")
500 script.Mount("/oem", recovery_mount_options)
501
502 def WriteDeviceAssertions(self, script, oem_no_mount):
503 # Read the property directly if not using OEM properties.
504 if not self.oem_props:
505 script.AssertDevice(self.device)
506 return
507
508 # Otherwise assert OEM properties.
509 if not self.oem_dicts:
510 raise ExternalError(
511 "No OEM file provided to answer expected assertions")
512
513 for prop in self.oem_props.split():
514 values = []
515 for oem_dict in self.oem_dicts:
516 if prop in oem_dict:
517 values.append(oem_dict[prop])
518 if not values:
519 raise ExternalError(
520 "The OEM file is missing the property %s" % (prop,))
521 script.AssertOemProperty(prop, values, oem_no_mount)
522
523
Tao Bao410ad8b2018-08-24 12:08:38 -0700524def LoadInfoDict(input_file, repacking=False):
525 """Loads the key/value pairs from the given input target_files.
526
527 It reads `META/misc_info.txt` file in the target_files input, does sanity
528 checks and returns the parsed key/value pairs for to the given build. It's
529 usually called early when working on input target_files files, e.g. when
530 generating OTAs, or signing builds. Note that the function may be called
531 against an old target_files file (i.e. from past dessert releases). So the
532 property parsing needs to be backward compatible.
533
534 In a `META/misc_info.txt`, a few properties are stored as links to the files
535 in the PRODUCT_OUT directory. It works fine with the build system. However,
536 they are no longer available when (re)generating images from target_files zip.
537 When `repacking` is True, redirect these properties to the actual files in the
538 unzipped directory.
539
540 Args:
541 input_file: The input target_files file, which could be an open
542 zipfile.ZipFile instance, or a str for the dir that contains the files
543 unzipped from a target_files file.
544 repacking: Whether it's trying repack an target_files file after loading the
545 info dict (default: False). If so, it will rewrite a few loaded
546 properties (e.g. selinux_fc, root_dir) to point to the actual files in
547 target_files file. When doing repacking, `input_file` must be a dir.
548
549 Returns:
550 A dict that contains the parsed key/value pairs.
551
552 Raises:
553 AssertionError: On invalid input arguments.
554 ValueError: On malformed input values.
555 """
556 if repacking:
557 assert isinstance(input_file, str), \
558 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700559
Doug Zongkerc9253822014-02-04 12:17:58 -0800560 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700561 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800562 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800563 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700564 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800565 try:
566 with open(path) as f:
567 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700568 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800569 if e.errno == errno.ENOENT:
570 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800571
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700572 try:
Michael Runge6e836112014-04-15 17:40:21 -0700573 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700574 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700575 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700576
Tao Bao410ad8b2018-08-24 12:08:38 -0700577 if "recovery_api_version" not in d:
578 raise ValueError("Failed to find 'recovery_api_version'")
579 if "fstab_version" not in d:
580 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800581
Tao Bao410ad8b2018-08-24 12:08:38 -0700582 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700583 # "selinux_fc" properties should point to the file_contexts files
584 # (file_contexts.bin) under META/.
585 for key in d:
586 if key.endswith("selinux_fc"):
587 fc_basename = os.path.basename(d[key])
588 fc_config = os.path.join(input_file, "META", fc_basename)
589 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700590
Daniel Norman72c626f2019-05-13 15:58:14 -0700591 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700592
Tom Cherryd14b8952018-08-09 14:26:00 -0700593 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700594 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700595 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700596 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700597
David Anderson0ec64ac2019-12-06 12:21:18 -0800598 # Redirect {partition}_base_fs_file for each of the named partitions.
599 for part_name in ["system", "vendor", "system_ext", "product", "odm"]:
600 key_name = part_name + "_base_fs_file"
601 if key_name not in d:
602 continue
603 basename = os.path.basename(d[key_name])
604 base_fs_file = os.path.join(input_file, "META", basename)
605 if os.path.exists(base_fs_file):
606 d[key_name] = base_fs_file
Tao Baob079b502016-05-03 08:01:19 -0700607 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700608 logger.warning(
David Anderson0ec64ac2019-12-06 12:21:18 -0800609 "Failed to find %s base fs file: %s", part_name, base_fs_file)
610 del d[key_name]
Tao Baof54216f2016-03-29 15:12:37 -0700611
Doug Zongker37974732010-09-16 17:44:38 -0700612 def makeint(key):
613 if key in d:
614 d[key] = int(d[key], 0)
615
616 makeint("recovery_api_version")
617 makeint("blocksize")
618 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700619 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700620 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700621 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700622 makeint("recovery_size")
623 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800624 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700625
Tao Bao765668f2019-10-04 22:03:00 -0700626 # Load recovery fstab if applicable.
627 d["fstab"] = _FindAndLoadRecoveryFstab(d, input_file, read_helper)
Tianjie Xucfa86222016-03-07 16:31:19 -0800628
Tianjie Xu861f4132018-09-12 11:49:33 -0700629 # Tries to load the build props for all partitions with care_map, including
630 # system and vendor.
631 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800632 partition_prop = "{}.build.prop".format(partition)
633 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700634 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800635 # Some partition might use /<partition>/etc/build.prop as the new path.
636 # TODO: try new path first when majority of them switch to the new path.
637 if not d[partition_prop]:
638 d[partition_prop] = LoadBuildProp(
639 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700640 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800641
Tao Bao3ed35d32019-10-07 20:48:48 -0700642 # Set up the salt (based on fingerprint) that will be used when adding AVB
643 # hash / hashtree footers.
Tao Bao12d87fc2018-01-31 12:18:52 -0800644 if d.get("avb_enable") == "true":
Tao Bao3ed35d32019-10-07 20:48:48 -0700645 build_info = BuildInfo(d)
646 d["avb_salt"] = sha256(build_info.fingerprint).hexdigest()
Tao Bao12d87fc2018-01-31 12:18:52 -0800647
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700648 return d
649
Tao Baod1de6f32017-03-01 16:38:48 -0800650
Tao Baobcd1d162017-08-26 13:10:26 -0700651def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700652 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700653 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700654 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700655 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700656 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700657 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700658
Tao Baod1de6f32017-03-01 16:38:48 -0800659
Daniel Norman4cc9df62019-07-18 10:11:07 -0700660def LoadListFromFile(file_path):
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900661 with open(file_path) as f:
Daniel Norman4cc9df62019-07-18 10:11:07 -0700662 return f.read().splitlines()
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900663
Daniel Norman4cc9df62019-07-18 10:11:07 -0700664
665def LoadDictionaryFromFile(file_path):
666 lines = LoadListFromFile(file_path)
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900667 return LoadDictionaryFromLines(lines)
668
669
Michael Runge6e836112014-04-15 17:40:21 -0700670def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700671 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700672 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700673 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700674 if not line or line.startswith("#"):
675 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700676 if "=" in line:
677 name, value = line.split("=", 1)
678 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700679 return d
680
Tao Baod1de6f32017-03-01 16:38:48 -0800681
Tianjie Xucfa86222016-03-07 16:31:19 -0800682def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
683 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700684 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800685 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700686 self.mount_point = mount_point
687 self.fs_type = fs_type
688 self.device = device
689 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700690 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700691
692 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800693 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700694 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700695 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700696 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700697
Tao Baod1de6f32017-03-01 16:38:48 -0800698 assert fstab_version == 2
699
700 d = {}
701 for line in data.split("\n"):
702 line = line.strip()
703 if not line or line.startswith("#"):
704 continue
705
706 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
707 pieces = line.split()
708 if len(pieces) != 5:
709 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
710
711 # Ignore entries that are managed by vold.
712 options = pieces[4]
713 if "voldmanaged=" in options:
714 continue
715
716 # It's a good line, parse it.
717 length = 0
718 options = options.split(",")
719 for i in options:
720 if i.startswith("length="):
721 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800722 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800723 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700724 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800725
Tao Baod1de6f32017-03-01 16:38:48 -0800726 mount_flags = pieces[3]
727 # Honor the SELinux context if present.
728 context = None
729 for i in mount_flags.split(","):
730 if i.startswith("context="):
731 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800732
Tao Baod1de6f32017-03-01 16:38:48 -0800733 mount_point = pieces[1]
734 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
735 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800736
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700737 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700738 # system. Other areas assume system is always at "/system" so point /system
739 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700740 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800741 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700742 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700743 return d
744
745
Tao Bao765668f2019-10-04 22:03:00 -0700746def _FindAndLoadRecoveryFstab(info_dict, input_file, read_helper):
747 """Finds the path to recovery fstab and loads its contents."""
748 # recovery fstab is only meaningful when installing an update via recovery
749 # (i.e. non-A/B OTA). Skip loading fstab if device used A/B OTA.
750 if info_dict.get('ab_update') == 'true':
751 return None
752
753 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
754 # ../RAMDISK/system/etc/recovery.fstab. This function has to handle both
755 # cases, since it may load the info_dict from an old build (e.g. when
756 # generating incremental OTAs from that build).
757 system_root_image = info_dict.get('system_root_image') == 'true'
758 if info_dict.get('no_recovery') != 'true':
759 recovery_fstab_path = 'RECOVERY/RAMDISK/system/etc/recovery.fstab'
760 if isinstance(input_file, zipfile.ZipFile):
761 if recovery_fstab_path not in input_file.namelist():
762 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
763 else:
764 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
765 if not os.path.exists(path):
766 recovery_fstab_path = 'RECOVERY/RAMDISK/etc/recovery.fstab'
767 return LoadRecoveryFSTab(
768 read_helper, info_dict['fstab_version'], recovery_fstab_path,
769 system_root_image)
770
771 if info_dict.get('recovery_as_boot') == 'true':
772 recovery_fstab_path = 'BOOT/RAMDISK/system/etc/recovery.fstab'
773 if isinstance(input_file, zipfile.ZipFile):
774 if recovery_fstab_path not in input_file.namelist():
775 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
776 else:
777 path = os.path.join(input_file, *recovery_fstab_path.split('/'))
778 if not os.path.exists(path):
779 recovery_fstab_path = 'BOOT/RAMDISK/etc/recovery.fstab'
780 return LoadRecoveryFSTab(
781 read_helper, info_dict['fstab_version'], recovery_fstab_path,
782 system_root_image)
783
784 return None
785
786
Doug Zongker37974732010-09-16 17:44:38 -0700787def DumpInfoDict(d):
788 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700789 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700790
Dan Albert8b72aef2015-03-23 19:13:21 -0700791
Daniel Norman55417142019-11-25 16:04:36 -0800792def MergeDynamicPartitionInfoDicts(framework_dict, vendor_dict):
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700793 """Merges dynamic partition info variables.
794
795 Args:
796 framework_dict: The dictionary of dynamic partition info variables from the
797 partial framework target files.
798 vendor_dict: The dictionary of dynamic partition info variables from the
799 partial vendor target files.
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700800
801 Returns:
802 The merged dynamic partition info dictionary.
803 """
804 merged_dict = {}
805 # Partition groups and group sizes are defined by the vendor dict because
806 # these values may vary for each board that uses a shared system image.
807 merged_dict["super_partition_groups"] = vendor_dict["super_partition_groups"]
Daniel Norman55417142019-11-25 16:04:36 -0800808 framework_dynamic_partition_list = framework_dict.get(
809 "dynamic_partition_list", "")
810 vendor_dynamic_partition_list = vendor_dict.get("dynamic_partition_list", "")
811 merged_dict["dynamic_partition_list"] = ("%s %s" % (
812 framework_dynamic_partition_list, vendor_dynamic_partition_list)).strip()
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700813 for partition_group in merged_dict["super_partition_groups"].split(" "):
814 # Set the partition group's size using the value from the vendor dict.
Daniel Norman55417142019-11-25 16:04:36 -0800815 key = "super_%s_group_size" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700816 if key not in vendor_dict:
817 raise ValueError("Vendor dict does not contain required key %s." % key)
818 merged_dict[key] = vendor_dict[key]
819
820 # Set the partition group's partition list using a concatenation of the
821 # framework and vendor partition lists.
Daniel Norman55417142019-11-25 16:04:36 -0800822 key = "super_%s_partition_list" % partition_group
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700823 merged_dict[key] = (
824 "%s %s" %
825 (framework_dict.get(key, ""), vendor_dict.get(key, ""))).strip()
P Adarsh Reddy7e9b5c42019-12-20 15:07:24 +0530826
827 # Pick virtual ab related flags from vendor dict, if defined.
828 if "virtual_ab" in vendor_dict.keys():
829 merged_dict["virtual_ab"] = vendor_dict["virtual_ab"]
830 if "virtual_ab_retrofit" in vendor_dict.keys():
831 merged_dict["virtual_ab_retrofit"] = vendor_dict["virtual_ab_retrofit"]
Daniel Normanbfc51ef2019-07-24 14:34:54 -0700832 return merged_dict
833
834
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800835def AppendAVBSigningArgs(cmd, partition):
836 """Append signing arguments for avbtool."""
837 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
838 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700839 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
840 new_key_path = os.path.join(OPTIONS.search_path, key_path)
841 if os.path.exists(new_key_path):
842 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800843 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
844 if key_path and algorithm:
845 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700846 avb_salt = OPTIONS.info_dict.get("avb_salt")
847 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700848 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700849 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800850
851
Tao Bao765668f2019-10-04 22:03:00 -0700852def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700853 """Returns the VBMeta arguments for partition.
854
855 It sets up the VBMeta argument by including the partition descriptor from the
856 given 'image', or by configuring the partition as a chained partition.
857
858 Args:
859 partition: The name of the partition (e.g. "system").
860 image: The path to the partition image.
861 info_dict: A dict returned by common.LoadInfoDict(). Will use
862 OPTIONS.info_dict if None has been given.
863
864 Returns:
865 A list of VBMeta arguments.
866 """
867 if info_dict is None:
868 info_dict = OPTIONS.info_dict
869
870 # Check if chain partition is used.
871 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +0800872 if not key_path:
873 return ["--include_descriptors_from_image", image]
874
875 # For a non-A/B device, we don't chain /recovery nor include its descriptor
876 # into vbmeta.img. The recovery image will be configured on an independent
877 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
878 # See details at
879 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -0700880 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +0800881 return []
882
883 # Otherwise chain the partition into vbmeta.
884 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
885 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -0700886
887
Tao Bao02a08592018-07-22 12:40:45 -0700888def GetAvbChainedPartitionArg(partition, info_dict, key=None):
889 """Constructs and returns the arg to build or verify a chained partition.
890
891 Args:
892 partition: The partition name.
893 info_dict: The info dict to look up the key info and rollback index
894 location.
895 key: The key to be used for building or verifying the partition. Defaults to
896 the key listed in info_dict.
897
898 Returns:
899 A string of form "partition:rollback_index_location:key" that can be used to
900 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700901 """
902 if key is None:
903 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700904 if key and not os.path.exists(key) and OPTIONS.search_path:
905 new_key_path = os.path.join(OPTIONS.search_path, key)
906 if os.path.exists(new_key_path):
907 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700908 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700909 rollback_index_location = info_dict[
910 "avb_" + partition + "_rollback_index_location"]
911 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
912
913
Daniel Norman276f0622019-07-26 14:13:51 -0700914def BuildVBMeta(image_path, partitions, name, needed_partitions):
915 """Creates a VBMeta image.
916
917 It generates the requested VBMeta image. The requested image could be for
918 top-level or chained VBMeta image, which is determined based on the name.
919
920 Args:
921 image_path: The output path for the new VBMeta image.
922 partitions: A dict that's keyed by partition names with image paths as
923 values. Only valid partition names are accepted, as listed in
924 common.AVB_PARTITIONS.
925 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
926 needed_partitions: Partitions whose descriptors should be included into the
927 generated VBMeta image.
928
929 Raises:
930 AssertionError: On invalid input args.
931 """
932 avbtool = OPTIONS.info_dict["avb_avbtool"]
933 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
934 AppendAVBSigningArgs(cmd, name)
935
936 for partition, path in partitions.items():
937 if partition not in needed_partitions:
938 continue
939 assert (partition in AVB_PARTITIONS or
940 partition in AVB_VBMETA_PARTITIONS), \
941 'Unknown partition: {}'.format(partition)
942 assert os.path.exists(path), \
943 'Failed to find {} for {}'.format(path, partition)
944 cmd.extend(GetAvbPartitionArg(partition, path))
945
946 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
947 if args and args.strip():
948 split_args = shlex.split(args)
949 for index, arg in enumerate(split_args[:-1]):
950 # Sanity check that the image file exists. Some images might be defined
951 # as a path relative to source tree, which may not be available at the
952 # same location when running this script (we have the input target_files
953 # zip only). For such cases, we additionally scan other locations (e.g.
954 # IMAGES/, RADIO/, etc) before bailing out.
955 if arg == '--include_descriptors_from_image':
956 image_path = split_args[index + 1]
957 if os.path.exists(image_path):
958 continue
959 found = False
960 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
961 alt_path = os.path.join(
962 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
963 if os.path.exists(alt_path):
964 split_args[index + 1] = alt_path
965 found = True
966 break
967 assert found, 'Failed to find {}'.format(image_path)
968 cmd.extend(split_args)
969
970 RunAndCheckOutput(cmd)
971
Dan Austin52903642019-12-12 15:44:00 -0800972 if OPTIONS.aftl_server is not None:
973 # Ensure the other AFTL parameters are set as well.
974 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
975 assert OPTIONS.aftl_manufacturer_key_path is not None, 'No AFTL manufacturer key provided.'
976 assert OPTIONS.aftl_signer_helper is not None, 'No AFTL signer helper provided.'
977 # AFTL inclusion proof generation code will go here.
Daniel Norman276f0622019-07-26 14:13:51 -0700978
Steve Mucklee1b10862019-07-10 10:49:37 -0700979def _MakeRamdisk(sourcedir, fs_config_file=None):
980 ramdisk_img = tempfile.NamedTemporaryFile()
981
982 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
983 cmd = ["mkbootfs", "-f", fs_config_file,
984 os.path.join(sourcedir, "RAMDISK")]
985 else:
986 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
987 p1 = Run(cmd, stdout=subprocess.PIPE)
988 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
989
990 p2.wait()
991 p1.wait()
992 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
993 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
994
995 return ramdisk_img
996
997
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700998def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800999 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001000 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001001
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001002 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -08001003 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
1004 we are building a two-step special image (i.e. building a recovery image to
1005 be loaded into /boot in two-step OTAs).
1006
1007 Return the image data, or None if sourcedir does not appear to contains files
1008 for building the requested image.
1009 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001010
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001011 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
1012 return None
1013
1014 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001015 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001016
Doug Zongkerd5131602012-08-02 14:46:42 -07001017 if info_dict is None:
1018 info_dict = OPTIONS.info_dict
1019
Doug Zongkereef39442009-04-02 12:14:19 -07001020 img = tempfile.NamedTemporaryFile()
1021
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001022 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001023 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001024
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001025 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1026 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1027
1028 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -07001029
Benoit Fradina45a8682014-07-14 21:00:43 +02001030 fn = os.path.join(sourcedir, "second")
1031 if os.access(fn, os.F_OK):
1032 cmd.append("--second")
1033 cmd.append(fn)
1034
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001035 fn = os.path.join(sourcedir, "dtb")
1036 if os.access(fn, os.F_OK):
1037 cmd.append("--dtb")
1038 cmd.append(fn)
1039
Doug Zongker171f1cd2009-06-15 22:36:37 -07001040 fn = os.path.join(sourcedir, "cmdline")
1041 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001042 cmd.append("--cmdline")
1043 cmd.append(open(fn).read().rstrip("\n"))
1044
1045 fn = os.path.join(sourcedir, "base")
1046 if os.access(fn, os.F_OK):
1047 cmd.append("--base")
1048 cmd.append(open(fn).read().rstrip("\n"))
1049
Ying Wang4de6b5b2010-08-25 14:29:34 -07001050 fn = os.path.join(sourcedir, "pagesize")
1051 if os.access(fn, os.F_OK):
1052 cmd.append("--pagesize")
1053 cmd.append(open(fn).read().rstrip("\n"))
1054
Tao Bao76def242017-11-21 09:25:31 -08001055 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001056 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001057 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001058
Tao Bao76def242017-11-21 09:25:31 -08001059 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001060 if args and args.strip():
1061 cmd.extend(shlex.split(args))
1062
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001063 if has_ramdisk:
1064 cmd.extend(["--ramdisk", ramdisk_img.name])
1065
Tao Baod95e9fd2015-03-29 23:07:41 -07001066 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001067 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001068 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001069 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001070 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001071 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001072
Tao Baobf70c312017-07-11 17:27:55 -07001073 # "boot" or "recovery", without extension.
1074 partition_name = os.path.basename(sourcedir).lower()
1075
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001076 if partition_name == "recovery":
1077 if info_dict.get("include_recovery_dtbo") == "true":
1078 fn = os.path.join(sourcedir, "recovery_dtbo")
1079 cmd.extend(["--recovery_dtbo", fn])
1080 if info_dict.get("include_recovery_acpio") == "true":
1081 fn = os.path.join(sourcedir, "recovery_acpio")
1082 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001083
Tao Bao986ee862018-10-04 15:46:16 -07001084 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001085
Tao Bao76def242017-11-21 09:25:31 -08001086 if (info_dict.get("boot_signer") == "true" and
1087 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001088 # Hard-code the path as "/boot" for two-step special recovery image (which
1089 # will be loaded into /boot during the two-step OTA).
1090 if two_step_image:
1091 path = "/boot"
1092 else:
Tao Baobf70c312017-07-11 17:27:55 -07001093 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001094 cmd = [OPTIONS.boot_signer_path]
1095 cmd.extend(OPTIONS.boot_signer_args)
1096 cmd.extend([path, img.name,
1097 info_dict["verity_key"] + ".pk8",
1098 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001099 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001100
Tao Baod95e9fd2015-03-29 23:07:41 -07001101 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001102 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -07001103 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001104 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001105 # We have switched from the prebuilt futility binary to using the tool
1106 # (futility-host) built from the source. Override the setting in the old
1107 # TF.zip.
1108 futility = info_dict["futility"]
1109 if futility.startswith("prebuilts/"):
1110 futility = "futility-host"
1111 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001112 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001113 info_dict["vboot_key"] + ".vbprivk",
1114 info_dict["vboot_subkey"] + ".vbprivk",
1115 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001116 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001117 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001118
Tao Baof3282b42015-04-01 11:21:55 -07001119 # Clean up the temp files.
1120 img_unsigned.close()
1121 img_keyblock.close()
1122
David Zeuthen8fecb282017-12-01 16:24:01 -05001123 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001124 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001125 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001126 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001127 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -07001128 "--partition_size", str(part_size), "--partition_name",
1129 partition_name]
1130 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001131 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001132 if args and args.strip():
1133 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001134 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001135
1136 img.seek(os.SEEK_SET, 0)
1137 data = img.read()
1138
1139 if has_ramdisk:
1140 ramdisk_img.close()
1141 img.close()
1142
1143 return data
1144
1145
Doug Zongkerd5131602012-08-02 14:46:42 -07001146def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001147 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001148 """Return a File object with the desired bootable image.
1149
1150 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1151 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1152 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001153
Doug Zongker55d93282011-01-25 17:03:34 -08001154 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1155 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001156 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001157 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001158
1159 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1160 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001161 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001162 return File.FromLocalFile(name, prebuilt_path)
1163
Tao Bao32fcdab2018-10-12 10:30:39 -07001164 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001165
1166 if info_dict is None:
1167 info_dict = OPTIONS.info_dict
1168
1169 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001170 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1171 # for recovery.
1172 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1173 prebuilt_name != "boot.img" or
1174 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001175
Doug Zongker6f1d0312014-08-22 08:07:12 -07001176 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001177 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
1178 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001179 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001180 if data:
1181 return File(name, data)
1182 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001183
Doug Zongkereef39442009-04-02 12:14:19 -07001184
Steve Mucklee1b10862019-07-10 10:49:37 -07001185def _BuildVendorBootImage(sourcedir, info_dict=None):
1186 """Build a vendor boot image from the specified sourcedir.
1187
1188 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1189 turn them into a vendor boot image.
1190
1191 Return the image data, or None if sourcedir does not appear to contains files
1192 for building the requested image.
1193 """
1194
1195 if info_dict is None:
1196 info_dict = OPTIONS.info_dict
1197
1198 img = tempfile.NamedTemporaryFile()
1199
1200 ramdisk_img = _MakeRamdisk(sourcedir)
1201
1202 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1203 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1204
1205 cmd = [mkbootimg]
1206
1207 fn = os.path.join(sourcedir, "dtb")
1208 if os.access(fn, os.F_OK):
1209 cmd.append("--dtb")
1210 cmd.append(fn)
1211
1212 fn = os.path.join(sourcedir, "vendor_cmdline")
1213 if os.access(fn, os.F_OK):
1214 cmd.append("--vendor_cmdline")
1215 cmd.append(open(fn).read().rstrip("\n"))
1216
1217 fn = os.path.join(sourcedir, "base")
1218 if os.access(fn, os.F_OK):
1219 cmd.append("--base")
1220 cmd.append(open(fn).read().rstrip("\n"))
1221
1222 fn = os.path.join(sourcedir, "pagesize")
1223 if os.access(fn, os.F_OK):
1224 cmd.append("--pagesize")
1225 cmd.append(open(fn).read().rstrip("\n"))
1226
1227 args = info_dict.get("mkbootimg_args")
1228 if args and args.strip():
1229 cmd.extend(shlex.split(args))
1230
1231 args = info_dict.get("mkbootimg_version_args")
1232 if args and args.strip():
1233 cmd.extend(shlex.split(args))
1234
1235 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1236 cmd.extend(["--vendor_boot", img.name])
1237
1238 RunAndCheckOutput(cmd)
1239
1240 # AVB: if enabled, calculate and add hash.
1241 if info_dict.get("avb_enable") == "true":
1242 avbtool = info_dict["avb_avbtool"]
1243 part_size = info_dict["vendor_boot_size"]
1244 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001245 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001246 AppendAVBSigningArgs(cmd, "vendor_boot")
1247 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1248 if args and args.strip():
1249 cmd.extend(shlex.split(args))
1250 RunAndCheckOutput(cmd)
1251
1252 img.seek(os.SEEK_SET, 0)
1253 data = img.read()
1254
1255 ramdisk_img.close()
1256 img.close()
1257
1258 return data
1259
1260
1261def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1262 info_dict=None):
1263 """Return a File object with the desired vendor boot image.
1264
1265 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1266 the source files in 'unpack_dir'/'tree_subdir'."""
1267
1268 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1269 if os.path.exists(prebuilt_path):
1270 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1271 return File.FromLocalFile(name, prebuilt_path)
1272
1273 logger.info("building image from target_files %s...", tree_subdir)
1274
1275 if info_dict is None:
1276 info_dict = OPTIONS.info_dict
1277
1278 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1279 if data:
1280 return File(name, data)
1281 return None
1282
1283
Narayan Kamatha07bf042017-08-14 14:49:21 +01001284def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001285 """Gunzips the given gzip compressed file to a given output file."""
1286 with gzip.open(in_filename, "rb") as in_file, \
1287 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001288 shutil.copyfileobj(in_file, out_file)
1289
1290
Tao Bao0ff15de2019-03-20 11:26:06 -07001291def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001292 """Unzips the archive to the given directory.
1293
1294 Args:
1295 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001296 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001297 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1298 archvie. Non-matching patterns will be filtered out. If there's no match
1299 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001300 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001301 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001302 if patterns is not None:
1303 # Filter out non-matching patterns. unzip will complain otherwise.
1304 with zipfile.ZipFile(filename) as input_zip:
1305 names = input_zip.namelist()
1306 filtered = [
1307 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1308
1309 # There isn't any matching files. Don't unzip anything.
1310 if not filtered:
1311 return
1312 cmd.extend(filtered)
1313
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001314 RunAndCheckOutput(cmd)
1315
1316
Doug Zongker75f17362009-12-08 13:46:44 -08001317def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001318 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001319
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001320 Args:
1321 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1322 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1323
1324 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1325 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001326
Tao Bao1c830bf2017-12-25 10:43:47 -08001327 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001328 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001329 """
Doug Zongkereef39442009-04-02 12:14:19 -07001330
Tao Bao1c830bf2017-12-25 10:43:47 -08001331 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001332 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1333 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001334 UnzipToDir(m.group(1), tmp, pattern)
1335 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001336 filename = m.group(1)
1337 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001338 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001339
Tao Baodba59ee2018-01-09 13:21:02 -08001340 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001341
1342
Yifan Hong8a66a712019-04-04 15:37:57 -07001343def GetUserImage(which, tmpdir, input_zip,
1344 info_dict=None,
1345 allow_shared_blocks=None,
1346 hashtree_info_generator=None,
1347 reset_file_map=False):
1348 """Returns an Image object suitable for passing to BlockImageDiff.
1349
1350 This function loads the specified image from the given path. If the specified
1351 image is sparse, it also performs additional processing for OTA purpose. For
1352 example, it always adds block 0 to clobbered blocks list. It also detects
1353 files that cannot be reconstructed from the block list, for whom we should
1354 avoid applying imgdiff.
1355
1356 Args:
1357 which: The partition name.
1358 tmpdir: The directory that contains the prebuilt image and block map file.
1359 input_zip: The target-files ZIP archive.
1360 info_dict: The dict to be looked up for relevant info.
1361 allow_shared_blocks: If image is sparse, whether having shared blocks is
1362 allowed. If none, it is looked up from info_dict.
1363 hashtree_info_generator: If present and image is sparse, generates the
1364 hashtree_info for this sparse image.
1365 reset_file_map: If true and image is sparse, reset file map before returning
1366 the image.
1367 Returns:
1368 A Image object. If it is a sparse image and reset_file_map is False, the
1369 image will have file_map info loaded.
1370 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001371 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001372 info_dict = LoadInfoDict(input_zip)
1373
1374 is_sparse = info_dict.get("extfs_sparse_flag")
1375
1376 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1377 # shared blocks (i.e. some blocks will show up in multiple files' block
1378 # list). We can only allocate such shared blocks to the first "owner", and
1379 # disable imgdiff for all later occurrences.
1380 if allow_shared_blocks is None:
1381 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1382
1383 if is_sparse:
1384 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1385 hashtree_info_generator)
1386 if reset_file_map:
1387 img.ResetFileMap()
1388 return img
1389 else:
1390 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1391
1392
1393def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1394 """Returns a Image object suitable for passing to BlockImageDiff.
1395
1396 This function loads the specified non-sparse image from the given path.
1397
1398 Args:
1399 which: The partition name.
1400 tmpdir: The directory that contains the prebuilt image and block map file.
1401 Returns:
1402 A Image object.
1403 """
1404 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1405 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1406
1407 # The image and map files must have been created prior to calling
1408 # ota_from_target_files.py (since LMP).
1409 assert os.path.exists(path) and os.path.exists(mappath)
1410
Tianjie Xu41976c72019-07-03 13:57:01 -07001411 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1412
Yifan Hong8a66a712019-04-04 15:37:57 -07001413
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001414def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1415 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001416 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1417
1418 This function loads the specified sparse image from the given path, and
1419 performs additional processing for OTA purpose. For example, it always adds
1420 block 0 to clobbered blocks list. It also detects files that cannot be
1421 reconstructed from the block list, for whom we should avoid applying imgdiff.
1422
1423 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001424 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001425 tmpdir: The directory that contains the prebuilt image and block map file.
1426 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001427 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001428 hashtree_info_generator: If present, generates the hashtree_info for this
1429 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001430 Returns:
1431 A SparseImage object, with file_map info loaded.
1432 """
Tao Baoc765cca2018-01-31 17:32:40 -08001433 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1434 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1435
1436 # The image and map files must have been created prior to calling
1437 # ota_from_target_files.py (since LMP).
1438 assert os.path.exists(path) and os.path.exists(mappath)
1439
1440 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1441 # it to clobbered_blocks so that it will be written to the target
1442 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1443 clobbered_blocks = "0"
1444
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001445 image = sparse_img.SparseImage(
1446 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1447 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001448
1449 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1450 # if they contain all zeros. We can't reconstruct such a file from its block
1451 # list. Tag such entries accordingly. (Bug: 65213616)
1452 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001453 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001454 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001455 continue
1456
Tom Cherryd14b8952018-08-09 14:26:00 -07001457 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1458 # filename listed in system.map may contain an additional leading slash
1459 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1460 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001461 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001462
Tom Cherryd14b8952018-08-09 14:26:00 -07001463 # Special handling another case, where files not under /system
1464 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001465 if which == 'system' and not arcname.startswith('SYSTEM'):
1466 arcname = 'ROOT/' + arcname
1467
1468 assert arcname in input_zip.namelist(), \
1469 "Failed to find the ZIP entry for {}".format(entry)
1470
Tao Baoc765cca2018-01-31 17:32:40 -08001471 info = input_zip.getinfo(arcname)
1472 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001473
1474 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001475 # image, check the original block list to determine its completeness. Note
1476 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001477 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001478 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001479
Tao Baoc765cca2018-01-31 17:32:40 -08001480 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1481 ranges.extra['incomplete'] = True
1482
1483 return image
1484
1485
Doug Zongkereef39442009-04-02 12:14:19 -07001486def GetKeyPasswords(keylist):
1487 """Given a list of keys, prompt the user to enter passwords for
1488 those which require them. Return a {key: password} dict. password
1489 will be None if the key has no password."""
1490
Doug Zongker8ce7c252009-05-22 13:34:54 -07001491 no_passwords = []
1492 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001493 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001494 devnull = open("/dev/null", "w+b")
1495 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001496 # We don't need a password for things that aren't really keys.
1497 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001498 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001499 continue
1500
T.R. Fullhart37e10522013-03-18 10:31:26 -07001501 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001502 "-inform", "DER", "-nocrypt"],
1503 stdin=devnull.fileno(),
1504 stdout=devnull.fileno(),
1505 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001506 p.communicate()
1507 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001508 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001509 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001510 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001511 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1512 "-inform", "DER", "-passin", "pass:"],
1513 stdin=devnull.fileno(),
1514 stdout=devnull.fileno(),
1515 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001516 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001517 if p.returncode == 0:
1518 # Encrypted key with empty string as password.
1519 key_passwords[k] = ''
1520 elif stderr.startswith('Error decrypting key'):
1521 # Definitely encrypted key.
1522 # It would have said "Error reading key" if it didn't parse correctly.
1523 need_passwords.append(k)
1524 else:
1525 # Potentially, a type of key that openssl doesn't understand.
1526 # We'll let the routines in signapk.jar handle it.
1527 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001528 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001529
T.R. Fullhart37e10522013-03-18 10:31:26 -07001530 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001531 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001532 return key_passwords
1533
1534
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001535def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001536 """Gets the minSdkVersion declared in the APK.
1537
changho.shin0f125362019-07-08 10:59:00 +09001538 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001539 This can be both a decimal number (API Level) or a codename.
1540
1541 Args:
1542 apk_name: The APK filename.
1543
1544 Returns:
1545 The parsed SDK version string.
1546
1547 Raises:
1548 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001549 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001550 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001551 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001552 stderr=subprocess.PIPE)
1553 stdoutdata, stderrdata = proc.communicate()
1554 if proc.returncode != 0:
1555 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001556 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001557 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001558
Tao Baof47bf0f2018-03-21 23:28:51 -07001559 for line in stdoutdata.split("\n"):
1560 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001561 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1562 if m:
1563 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001564 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001565
1566
1567def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001568 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001569
Tao Baof47bf0f2018-03-21 23:28:51 -07001570 If minSdkVersion is set to a codename, it is translated to a number using the
1571 provided map.
1572
1573 Args:
1574 apk_name: The APK filename.
1575
1576 Returns:
1577 The parsed SDK version number.
1578
1579 Raises:
1580 ExternalError: On failing to get the min SDK version number.
1581 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001582 version = GetMinSdkVersion(apk_name)
1583 try:
1584 return int(version)
1585 except ValueError:
1586 # Not a decimal number. Codename?
1587 if version in codename_to_api_level_map:
1588 return codename_to_api_level_map[version]
1589 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001590 raise ExternalError(
1591 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1592 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001593
1594
1595def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001596 codename_to_api_level_map=None, whole_file=False,
1597 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001598 """Sign the input_name zip/jar/apk, producing output_name. Use the
1599 given key and password (the latter may be None if the key does not
1600 have a password.
1601
Doug Zongker951495f2009-08-14 12:44:19 -07001602 If whole_file is true, use the "-w" option to SignApk to embed a
1603 signature that covers the whole file in the archive comment of the
1604 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001605
1606 min_api_level is the API Level (int) of the oldest platform this file may end
1607 up on. If not specified for an APK, the API Level is obtained by interpreting
1608 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1609
1610 codename_to_api_level_map is needed to translate the codename which may be
1611 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001612
1613 Caller may optionally specify extra args to be passed to SignApk, which
1614 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001615 """
Tao Bao76def242017-11-21 09:25:31 -08001616 if codename_to_api_level_map is None:
1617 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001618 if extra_signapk_args is None:
1619 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001620
Alex Klyubin9667b182015-12-10 13:38:50 -08001621 java_library_path = os.path.join(
1622 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1623
Tao Baoe95540e2016-11-08 12:08:53 -08001624 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1625 ["-Djava.library.path=" + java_library_path,
1626 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001627 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001628 if whole_file:
1629 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001630
1631 min_sdk_version = min_api_level
1632 if min_sdk_version is None:
1633 if not whole_file:
1634 min_sdk_version = GetMinSdkVersionInt(
1635 input_name, codename_to_api_level_map)
1636 if min_sdk_version is not None:
1637 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1638
T.R. Fullhart37e10522013-03-18 10:31:26 -07001639 cmd.extend([key + OPTIONS.public_key_suffix,
1640 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001641 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001642
Tao Bao73dd4f42018-10-04 16:25:33 -07001643 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001644 if password is not None:
1645 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001646 stdoutdata, _ = proc.communicate(password)
1647 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001648 raise ExternalError(
1649 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001650 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001651
Doug Zongkereef39442009-04-02 12:14:19 -07001652
Doug Zongker37974732010-09-16 17:44:38 -07001653def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001654 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001655
Tao Bao9dd909e2017-11-14 11:27:32 -08001656 For non-AVB images, raise exception if the data is too big. Print a warning
1657 if the data is nearing the maximum size.
1658
1659 For AVB images, the actual image size should be identical to the limit.
1660
1661 Args:
1662 data: A string that contains all the data for the partition.
1663 target: The partition name. The ".img" suffix is optional.
1664 info_dict: The dict to be looked up for relevant info.
1665 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001666 if target.endswith(".img"):
1667 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001668 mount_point = "/" + target
1669
Ying Wangf8824af2014-06-03 14:07:27 -07001670 fs_type = None
1671 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001672 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001673 if mount_point == "/userdata":
1674 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001675 p = info_dict["fstab"][mount_point]
1676 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001677 device = p.device
1678 if "/" in device:
1679 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001680 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001681 if not fs_type or not limit:
1682 return
Doug Zongkereef39442009-04-02 12:14:19 -07001683
Andrew Boie0f9aec82012-02-14 09:32:52 -08001684 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001685 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1686 # path.
1687 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1688 if size != limit:
1689 raise ExternalError(
1690 "Mismatching image size for %s: expected %d actual %d" % (
1691 target, limit, size))
1692 else:
1693 pct = float(size) * 100.0 / limit
1694 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1695 if pct >= 99.0:
1696 raise ExternalError(msg)
1697 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001698 logger.warning("\n WARNING: %s\n", msg)
1699 else:
1700 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001701
1702
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001703def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001704 """Parses the APK certs info from a given target-files zip.
1705
1706 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1707 tuple with the following elements: (1) a dictionary that maps packages to
1708 certs (based on the "certificate" and "private_key" attributes in the file;
1709 (2) a string representing the extension of compressed APKs in the target files
1710 (e.g ".gz", ".bro").
1711
1712 Args:
1713 tf_zip: The input target_files ZipFile (already open).
1714
1715 Returns:
1716 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1717 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1718 no compressed APKs.
1719 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001720 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001721 compressed_extension = None
1722
Tao Bao0f990332017-09-08 19:02:54 -07001723 # META/apkcerts.txt contains the info for _all_ the packages known at build
1724 # time. Filter out the ones that are not installed.
1725 installed_files = set()
1726 for name in tf_zip.namelist():
1727 basename = os.path.basename(name)
1728 if basename:
1729 installed_files.add(basename)
1730
Tao Baoda30cfa2017-12-01 16:19:46 -08001731 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001732 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001733 if not line:
1734 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001735 m = re.match(
1736 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1737 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1738 line)
1739 if not m:
1740 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001741
Tao Bao818ddf52018-01-05 11:17:34 -08001742 matches = m.groupdict()
1743 cert = matches["CERT"]
1744 privkey = matches["PRIVKEY"]
1745 name = matches["NAME"]
1746 this_compressed_extension = matches["COMPRESSED"]
1747
1748 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1749 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1750 if cert in SPECIAL_CERT_STRINGS and not privkey:
1751 certmap[name] = cert
1752 elif (cert.endswith(OPTIONS.public_key_suffix) and
1753 privkey.endswith(OPTIONS.private_key_suffix) and
1754 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1755 certmap[name] = cert[:-public_key_suffix_len]
1756 else:
1757 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1758
1759 if not this_compressed_extension:
1760 continue
1761
1762 # Only count the installed files.
1763 filename = name + '.' + this_compressed_extension
1764 if filename not in installed_files:
1765 continue
1766
1767 # Make sure that all the values in the compression map have the same
1768 # extension. We don't support multiple compression methods in the same
1769 # system image.
1770 if compressed_extension:
1771 if this_compressed_extension != compressed_extension:
1772 raise ValueError(
1773 "Multiple compressed extensions: {} vs {}".format(
1774 compressed_extension, this_compressed_extension))
1775 else:
1776 compressed_extension = this_compressed_extension
1777
1778 return (certmap,
1779 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001780
1781
Doug Zongkereef39442009-04-02 12:14:19 -07001782COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001783Global options
1784
1785 -p (--path) <dir>
1786 Prepend <dir>/bin to the list of places to search for binaries run by this
1787 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001788
Doug Zongker05d3dea2009-06-22 11:32:31 -07001789 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001790 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001791
Tao Bao30df8b42018-04-23 15:32:53 -07001792 -x (--extra) <key=value>
1793 Add a key/value pair to the 'extras' dict, which device-specific extension
1794 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001795
Doug Zongkereef39442009-04-02 12:14:19 -07001796 -v (--verbose)
1797 Show command lines being executed.
1798
1799 -h (--help)
1800 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001801
1802 --logfile <file>
1803 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001804"""
1805
1806def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001807 print(docstring.rstrip("\n"))
1808 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001809
1810
1811def ParseOptions(argv,
1812 docstring,
1813 extra_opts="", extra_long_opts=(),
1814 extra_option_handler=None):
1815 """Parse the options in argv and return any arguments that aren't
1816 flags. docstring is the calling module's docstring, to be displayed
1817 for errors and -h. extra_opts and extra_long_opts are for flags
1818 defined by the caller, which are processed by passing them to
1819 extra_option_handler."""
1820
1821 try:
1822 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001823 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001824 ["help", "verbose", "path=", "signapk_path=",
1825 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001826 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001827 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1828 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Dan Austin52903642019-12-12 15:44:00 -08001829 "extra=", "logfile=", "aftl_server=", "aftl_key_path=",
1830 "aftl_manufacturer_key_path=", "aftl_signer_helper="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001831 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001832 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001833 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001834 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001835 sys.exit(2)
1836
Doug Zongkereef39442009-04-02 12:14:19 -07001837 for o, a in opts:
1838 if o in ("-h", "--help"):
1839 Usage(docstring)
1840 sys.exit()
1841 elif o in ("-v", "--verbose"):
1842 OPTIONS.verbose = True
1843 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001844 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001845 elif o in ("--signapk_path",):
1846 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001847 elif o in ("--signapk_shared_library_path",):
1848 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001849 elif o in ("--extra_signapk_args",):
1850 OPTIONS.extra_signapk_args = shlex.split(a)
1851 elif o in ("--java_path",):
1852 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001853 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001854 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001855 elif o in ("--public_key_suffix",):
1856 OPTIONS.public_key_suffix = a
1857 elif o in ("--private_key_suffix",):
1858 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001859 elif o in ("--boot_signer_path",):
1860 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001861 elif o in ("--boot_signer_args",):
1862 OPTIONS.boot_signer_args = shlex.split(a)
1863 elif o in ("--verity_signer_path",):
1864 OPTIONS.verity_signer_path = a
1865 elif o in ("--verity_signer_args",):
1866 OPTIONS.verity_signer_args = shlex.split(a)
Dan Austin52903642019-12-12 15:44:00 -08001867 elif o in ("--aftl_server",):
1868 OPTIONS.aftl_server = a
1869 elif o in ("--aftl_key_path",):
1870 OPTIONS.aftl_key_path = a
1871 elif o in ("--aftl_manufacturer_key_path",):
1872 OPTIONS.aftl_manufacturer_key_path = a
1873 elif o in ("--aftl_signer_helper",):
1874 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07001875 elif o in ("-s", "--device_specific"):
1876 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001877 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001878 key, value = a.split("=", 1)
1879 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001880 elif o in ("--logfile",):
1881 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001882 else:
1883 if extra_option_handler is None or not extra_option_handler(o, a):
1884 assert False, "unknown option \"%s\"" % (o,)
1885
Doug Zongker85448772014-09-09 14:59:20 -07001886 if OPTIONS.search_path:
1887 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1888 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001889
1890 return args
1891
1892
Tao Bao4c851b12016-09-19 13:54:38 -07001893def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001894 """Make a temp file and add it to the list of things to be deleted
1895 when Cleanup() is called. Return the filename."""
1896 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1897 os.close(fd)
1898 OPTIONS.tempfiles.append(fn)
1899 return fn
1900
1901
Tao Bao1c830bf2017-12-25 10:43:47 -08001902def MakeTempDir(prefix='tmp', suffix=''):
1903 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1904
1905 Returns:
1906 The absolute pathname of the new directory.
1907 """
1908 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1909 OPTIONS.tempfiles.append(dir_name)
1910 return dir_name
1911
1912
Doug Zongkereef39442009-04-02 12:14:19 -07001913def Cleanup():
1914 for i in OPTIONS.tempfiles:
1915 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001916 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001917 else:
1918 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001919 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001920
1921
1922class PasswordManager(object):
1923 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001924 self.editor = os.getenv("EDITOR")
1925 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001926
1927 def GetPasswords(self, items):
1928 """Get passwords corresponding to each string in 'items',
1929 returning a dict. (The dict may have keys in addition to the
1930 values in 'items'.)
1931
1932 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1933 user edit that file to add more needed passwords. If no editor is
1934 available, or $ANDROID_PW_FILE isn't define, prompts the user
1935 interactively in the ordinary way.
1936 """
1937
1938 current = self.ReadFile()
1939
1940 first = True
1941 while True:
1942 missing = []
1943 for i in items:
1944 if i not in current or not current[i]:
1945 missing.append(i)
1946 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001947 if not missing:
1948 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001949
1950 for i in missing:
1951 current[i] = ""
1952
1953 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001954 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001955 if sys.version_info[0] >= 3:
1956 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001957 answer = raw_input("try to edit again? [y]> ").strip()
1958 if answer and answer[0] not in 'yY':
1959 raise RuntimeError("key passwords unavailable")
1960 first = False
1961
1962 current = self.UpdateAndReadFile(current)
1963
Dan Albert8b72aef2015-03-23 19:13:21 -07001964 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001965 """Prompt the user to enter a value (password) for each key in
1966 'current' whose value is fales. Returns a new dict with all the
1967 values.
1968 """
1969 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001970 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001971 if v:
1972 result[k] = v
1973 else:
1974 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001975 result[k] = getpass.getpass(
1976 "Enter password for %s key> " % k).strip()
1977 if result[k]:
1978 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001979 return result
1980
1981 def UpdateAndReadFile(self, current):
1982 if not self.editor or not self.pwfile:
1983 return self.PromptResult(current)
1984
1985 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001986 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001987 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1988 f.write("# (Additional spaces are harmless.)\n\n")
1989
1990 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001991 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001992 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001993 f.write("[[[ %s ]]] %s\n" % (v, k))
1994 if not v and first_line is None:
1995 # position cursor on first line with no password.
1996 first_line = i + 4
1997 f.close()
1998
Tao Bao986ee862018-10-04 15:46:16 -07001999 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07002000
2001 return self.ReadFile()
2002
2003 def ReadFile(self):
2004 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07002005 if self.pwfile is None:
2006 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002007 try:
2008 f = open(self.pwfile, "r")
2009 for line in f:
2010 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002011 if not line or line[0] == '#':
2012 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002013 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2014 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002015 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002016 else:
2017 result[m.group(2)] = m.group(1)
2018 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002019 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002020 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002021 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002022 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002023
2024
Dan Albert8e0178d2015-01-27 15:53:15 -08002025def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2026 compress_type=None):
2027 import datetime
2028
2029 # http://b/18015246
2030 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2031 # for files larger than 2GiB. We can work around this by adjusting their
2032 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2033 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2034 # it isn't clear to me exactly what circumstances cause this).
2035 # `zipfile.write()` must be used directly to work around this.
2036 #
2037 # This mess can be avoided if we port to python3.
2038 saved_zip64_limit = zipfile.ZIP64_LIMIT
2039 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2040
2041 if compress_type is None:
2042 compress_type = zip_file.compression
2043 if arcname is None:
2044 arcname = filename
2045
2046 saved_stat = os.stat(filename)
2047
2048 try:
2049 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2050 # file to be zipped and reset it when we're done.
2051 os.chmod(filename, perms)
2052
2053 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002054 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2055 # intentional. zip stores datetimes in local time without a time zone
2056 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2057 # in the zip archive.
2058 local_epoch = datetime.datetime.fromtimestamp(0)
2059 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002060 os.utime(filename, (timestamp, timestamp))
2061
2062 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2063 finally:
2064 os.chmod(filename, saved_stat.st_mode)
2065 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2066 zipfile.ZIP64_LIMIT = saved_zip64_limit
2067
2068
Tao Bao58c1b962015-05-20 09:32:18 -07002069def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002070 compress_type=None):
2071 """Wrap zipfile.writestr() function to work around the zip64 limit.
2072
2073 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2074 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2075 when calling crc32(bytes).
2076
2077 But it still works fine to write a shorter string into a large zip file.
2078 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2079 when we know the string won't be too long.
2080 """
2081
2082 saved_zip64_limit = zipfile.ZIP64_LIMIT
2083 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2084
2085 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2086 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002087 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002088 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002089 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002090 else:
Tao Baof3282b42015-04-01 11:21:55 -07002091 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002092 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2093 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2094 # such a case (since
2095 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2096 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2097 # permission bits. We follow the logic in Python 3 to get consistent
2098 # behavior between using the two versions.
2099 if not zinfo.external_attr:
2100 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002101
2102 # If compress_type is given, it overrides the value in zinfo.
2103 if compress_type is not None:
2104 zinfo.compress_type = compress_type
2105
Tao Bao58c1b962015-05-20 09:32:18 -07002106 # If perms is given, it has a priority.
2107 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002108 # If perms doesn't set the file type, mark it as a regular file.
2109 if perms & 0o770000 == 0:
2110 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002111 zinfo.external_attr = perms << 16
2112
Tao Baof3282b42015-04-01 11:21:55 -07002113 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002114 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2115
Dan Albert8b72aef2015-03-23 19:13:21 -07002116 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002117 zipfile.ZIP64_LIMIT = saved_zip64_limit
2118
2119
Tao Bao89d7ab22017-12-14 17:05:33 -08002120def ZipDelete(zip_filename, entries):
2121 """Deletes entries from a ZIP file.
2122
2123 Since deleting entries from a ZIP file is not supported, it shells out to
2124 'zip -d'.
2125
2126 Args:
2127 zip_filename: The name of the ZIP file.
2128 entries: The name of the entry, or the list of names to be deleted.
2129
2130 Raises:
2131 AssertionError: In case of non-zero return from 'zip'.
2132 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002133 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002134 entries = [entries]
2135 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002136 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002137
2138
Tao Baof3282b42015-04-01 11:21:55 -07002139def ZipClose(zip_file):
2140 # http://b/18015246
2141 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2142 # central directory.
2143 saved_zip64_limit = zipfile.ZIP64_LIMIT
2144 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2145
2146 zip_file.close()
2147
2148 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002149
2150
2151class DeviceSpecificParams(object):
2152 module = None
2153 def __init__(self, **kwargs):
2154 """Keyword arguments to the constructor become attributes of this
2155 object, which is passed to all functions in the device-specific
2156 module."""
Tao Bao38884282019-07-10 22:20:56 -07002157 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002158 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002159 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002160
2161 if self.module is None:
2162 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002163 if not path:
2164 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002165 try:
2166 if os.path.isdir(path):
2167 info = imp.find_module("releasetools", [path])
2168 else:
2169 d, f = os.path.split(path)
2170 b, x = os.path.splitext(f)
2171 if x == ".py":
2172 f = b
2173 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002174 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002175 self.module = imp.load_module("device_specific", *info)
2176 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002177 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002178
2179 def _DoCall(self, function_name, *args, **kwargs):
2180 """Call the named function in the device-specific module, passing
2181 the given args and kwargs. The first argument to the call will be
2182 the DeviceSpecific object itself. If there is no module, or the
2183 module does not define the function, return the value of the
2184 'default' kwarg (which itself defaults to None)."""
2185 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002186 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002187 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2188
2189 def FullOTA_Assertions(self):
2190 """Called after emitting the block of assertions at the top of a
2191 full OTA package. Implementations can add whatever additional
2192 assertions they like."""
2193 return self._DoCall("FullOTA_Assertions")
2194
Doug Zongkere5ff5902012-01-17 10:55:37 -08002195 def FullOTA_InstallBegin(self):
2196 """Called at the start of full OTA installation."""
2197 return self._DoCall("FullOTA_InstallBegin")
2198
Yifan Hong10c530d2018-12-27 17:34:18 -08002199 def FullOTA_GetBlockDifferences(self):
2200 """Called during full OTA installation and verification.
2201 Implementation should return a list of BlockDifference objects describing
2202 the update on each additional partitions.
2203 """
2204 return self._DoCall("FullOTA_GetBlockDifferences")
2205
Doug Zongker05d3dea2009-06-22 11:32:31 -07002206 def FullOTA_InstallEnd(self):
2207 """Called at the end of full OTA installation; typically this is
2208 used to install the image for the device's baseband processor."""
2209 return self._DoCall("FullOTA_InstallEnd")
2210
2211 def IncrementalOTA_Assertions(self):
2212 """Called after emitting the block of assertions at the top of an
2213 incremental OTA package. Implementations can add whatever
2214 additional assertions they like."""
2215 return self._DoCall("IncrementalOTA_Assertions")
2216
Doug Zongkere5ff5902012-01-17 10:55:37 -08002217 def IncrementalOTA_VerifyBegin(self):
2218 """Called at the start of the verification phase of incremental
2219 OTA installation; additional checks can be placed here to abort
2220 the script before any changes are made."""
2221 return self._DoCall("IncrementalOTA_VerifyBegin")
2222
Doug Zongker05d3dea2009-06-22 11:32:31 -07002223 def IncrementalOTA_VerifyEnd(self):
2224 """Called at the end of the verification phase of incremental OTA
2225 installation; additional checks can be placed here to abort the
2226 script before any changes are made."""
2227 return self._DoCall("IncrementalOTA_VerifyEnd")
2228
Doug Zongkere5ff5902012-01-17 10:55:37 -08002229 def IncrementalOTA_InstallBegin(self):
2230 """Called at the start of incremental OTA installation (after
2231 verification is complete)."""
2232 return self._DoCall("IncrementalOTA_InstallBegin")
2233
Yifan Hong10c530d2018-12-27 17:34:18 -08002234 def IncrementalOTA_GetBlockDifferences(self):
2235 """Called during incremental OTA installation and verification.
2236 Implementation should return a list of BlockDifference objects describing
2237 the update on each additional partitions.
2238 """
2239 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2240
Doug Zongker05d3dea2009-06-22 11:32:31 -07002241 def IncrementalOTA_InstallEnd(self):
2242 """Called at the end of incremental OTA installation; typically
2243 this is used to install the image for the device's baseband
2244 processor."""
2245 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002246
Tao Bao9bc6bb22015-11-09 16:58:28 -08002247 def VerifyOTA_Assertions(self):
2248 return self._DoCall("VerifyOTA_Assertions")
2249
Tao Bao76def242017-11-21 09:25:31 -08002250
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002251class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002252 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002253 self.name = name
2254 self.data = data
2255 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002256 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002257 self.sha1 = sha1(data).hexdigest()
2258
2259 @classmethod
2260 def FromLocalFile(cls, name, diskname):
2261 f = open(diskname, "rb")
2262 data = f.read()
2263 f.close()
2264 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002265
2266 def WriteToTemp(self):
2267 t = tempfile.NamedTemporaryFile()
2268 t.write(self.data)
2269 t.flush()
2270 return t
2271
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002272 def WriteToDir(self, d):
2273 with open(os.path.join(d, self.name), "wb") as fp:
2274 fp.write(self.data)
2275
Geremy Condra36bd3652014-02-06 19:45:10 -08002276 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002277 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002278
Tao Bao76def242017-11-21 09:25:31 -08002279
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002280DIFF_PROGRAM_BY_EXT = {
2281 ".gz" : "imgdiff",
2282 ".zip" : ["imgdiff", "-z"],
2283 ".jar" : ["imgdiff", "-z"],
2284 ".apk" : ["imgdiff", "-z"],
2285 ".img" : "imgdiff",
2286 }
2287
Tao Bao76def242017-11-21 09:25:31 -08002288
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002289class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002290 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002291 self.tf = tf
2292 self.sf = sf
2293 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002294 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002295
2296 def ComputePatch(self):
2297 """Compute the patch (as a string of data) needed to turn sf into
2298 tf. Returns the same tuple as GetPatch()."""
2299
2300 tf = self.tf
2301 sf = self.sf
2302
Doug Zongker24cd2802012-08-14 16:36:15 -07002303 if self.diff_program:
2304 diff_program = self.diff_program
2305 else:
2306 ext = os.path.splitext(tf.name)[1]
2307 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002308
2309 ttemp = tf.WriteToTemp()
2310 stemp = sf.WriteToTemp()
2311
2312 ext = os.path.splitext(tf.name)[1]
2313
2314 try:
2315 ptemp = tempfile.NamedTemporaryFile()
2316 if isinstance(diff_program, list):
2317 cmd = copy.copy(diff_program)
2318 else:
2319 cmd = [diff_program]
2320 cmd.append(stemp.name)
2321 cmd.append(ttemp.name)
2322 cmd.append(ptemp.name)
2323 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002324 err = []
2325 def run():
2326 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002327 if e:
2328 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002329 th = threading.Thread(target=run)
2330 th.start()
2331 th.join(timeout=300) # 5 mins
2332 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002333 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002334 p.terminate()
2335 th.join(5)
2336 if th.is_alive():
2337 p.kill()
2338 th.join()
2339
Tianjie Xua2a9f992018-01-05 15:15:54 -08002340 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002341 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002342 self.patch = None
2343 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002344 diff = ptemp.read()
2345 finally:
2346 ptemp.close()
2347 stemp.close()
2348 ttemp.close()
2349
2350 self.patch = diff
2351 return self.tf, self.sf, self.patch
2352
2353
2354 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002355 """Returns a tuple of (target_file, source_file, patch_data).
2356
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002357 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002358 computing the patch failed.
2359 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002360 return self.tf, self.sf, self.patch
2361
2362
2363def ComputeDifferences(diffs):
2364 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002365 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002366
2367 # Do the largest files first, to try and reduce the long-pole effect.
2368 by_size = [(i.tf.size, i) for i in diffs]
2369 by_size.sort(reverse=True)
2370 by_size = [i[1] for i in by_size]
2371
2372 lock = threading.Lock()
2373 diff_iter = iter(by_size) # accessed under lock
2374
2375 def worker():
2376 try:
2377 lock.acquire()
2378 for d in diff_iter:
2379 lock.release()
2380 start = time.time()
2381 d.ComputePatch()
2382 dur = time.time() - start
2383 lock.acquire()
2384
2385 tf, sf, patch = d.GetPatch()
2386 if sf.name == tf.name:
2387 name = tf.name
2388 else:
2389 name = "%s (%s)" % (tf.name, sf.name)
2390 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002391 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002392 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002393 logger.info(
2394 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2395 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002396 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002397 except Exception:
2398 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002399 raise
2400
2401 # start worker threads; wait for them all to finish.
2402 threads = [threading.Thread(target=worker)
2403 for i in range(OPTIONS.worker_threads)]
2404 for th in threads:
2405 th.start()
2406 while threads:
2407 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002408
2409
Dan Albert8b72aef2015-03-23 19:13:21 -07002410class BlockDifference(object):
2411 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002412 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002413 self.tgt = tgt
2414 self.src = src
2415 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002416 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002417 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002418
Tao Baodd2a5892015-03-12 12:32:37 -07002419 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002420 version = max(
2421 int(i) for i in
2422 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002423 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002424 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002425
Tianjie Xu41976c72019-07-03 13:57:01 -07002426 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2427 version=self.version,
2428 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002429 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002430 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002431 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002432 self.touched_src_ranges = b.touched_src_ranges
2433 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002434
Yifan Hong10c530d2018-12-27 17:34:18 -08002435 # On devices with dynamic partitions, for new partitions,
2436 # src is None but OPTIONS.source_info_dict is not.
2437 if OPTIONS.source_info_dict is None:
2438 is_dynamic_build = OPTIONS.info_dict.get(
2439 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002440 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002441 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002442 is_dynamic_build = OPTIONS.source_info_dict.get(
2443 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002444 is_dynamic_source = partition in shlex.split(
2445 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002446
Yifan Hongbb2658d2019-01-25 12:30:58 -08002447 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002448 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2449
Yifan Hongbb2658d2019-01-25 12:30:58 -08002450 # For dynamic partitions builds, check partition list in both source
2451 # and target build because new partitions may be added, and existing
2452 # partitions may be removed.
2453 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2454
Yifan Hong10c530d2018-12-27 17:34:18 -08002455 if is_dynamic:
2456 self.device = 'map_partition("%s")' % partition
2457 else:
2458 if OPTIONS.source_info_dict is None:
2459 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2460 else:
2461 _, device_path = GetTypeAndDevice("/" + partition,
2462 OPTIONS.source_info_dict)
2463 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002464
Tao Baod8d14be2016-02-04 14:26:02 -08002465 @property
2466 def required_cache(self):
2467 return self._required_cache
2468
Tao Bao76def242017-11-21 09:25:31 -08002469 def WriteScript(self, script, output_zip, progress=None,
2470 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002471 if not self.src:
2472 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002473 script.Print("Patching %s image unconditionally..." % (self.partition,))
2474 else:
2475 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002476
Dan Albert8b72aef2015-03-23 19:13:21 -07002477 if progress:
2478 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002479 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002480
2481 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002482 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002483
Tao Bao9bc6bb22015-11-09 16:58:28 -08002484 def WriteStrictVerifyScript(self, script):
2485 """Verify all the blocks in the care_map, including clobbered blocks.
2486
2487 This differs from the WriteVerifyScript() function: a) it prints different
2488 error messages; b) it doesn't allow half-way updated images to pass the
2489 verification."""
2490
2491 partition = self.partition
2492 script.Print("Verifying %s..." % (partition,))
2493 ranges = self.tgt.care_map
2494 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002495 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002496 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2497 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002498 self.device, ranges_str,
2499 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002500 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002501 script.AppendExtra("")
2502
Tao Baod522bdc2016-04-12 15:53:16 -07002503 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002504 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002505
2506 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002507 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002508 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002509
2510 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002511 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002512 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002513 ranges = self.touched_src_ranges
2514 expected_sha1 = self.touched_src_sha1
2515 else:
2516 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2517 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002518
2519 # No blocks to be checked, skipping.
2520 if not ranges:
2521 return
2522
Tao Bao5ece99d2015-05-12 11:42:31 -07002523 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002524 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002525 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002526 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2527 '"%s.patch.dat")) then' % (
2528 self.device, ranges_str, expected_sha1,
2529 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002530 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002531 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002532
Tianjie Xufc3422a2015-12-15 11:53:59 -08002533 if self.version >= 4:
2534
2535 # Bug: 21124327
2536 # When generating incrementals for the system and vendor partitions in
2537 # version 4 or newer, explicitly check the first block (which contains
2538 # the superblock) of the partition to see if it's what we expect. If
2539 # this check fails, give an explicit log message about the partition
2540 # having been remounted R/W (the most likely explanation).
2541 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002542 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002543
2544 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002545 if partition == "system":
2546 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2547 else:
2548 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002549 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002550 'ifelse (block_image_recover({device}, "{ranges}") && '
2551 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002552 'package_extract_file("{partition}.transfer.list"), '
2553 '"{partition}.new.dat", "{partition}.patch.dat"), '
2554 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002555 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002556 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002557 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002558
Tao Baodd2a5892015-03-12 12:32:37 -07002559 # Abort the OTA update. Note that the incremental OTA cannot be applied
2560 # even if it may match the checksum of the target partition.
2561 # a) If version < 3, operations like move and erase will make changes
2562 # unconditionally and damage the partition.
2563 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002564 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002565 if partition == "system":
2566 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2567 else:
2568 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2569 script.AppendExtra((
2570 'abort("E%d: %s partition has unexpected contents");\n'
2571 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002572
Yifan Hong10c530d2018-12-27 17:34:18 -08002573 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002574 partition = self.partition
2575 script.Print('Verifying the updated %s image...' % (partition,))
2576 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2577 ranges = self.tgt.care_map
2578 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002579 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002580 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002581 self.device, ranges_str,
2582 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002583
2584 # Bug: 20881595
2585 # Verify that extended blocks are really zeroed out.
2586 if self.tgt.extended:
2587 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002588 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002589 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002590 self.device, ranges_str,
2591 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002592 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002593 if partition == "system":
2594 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2595 else:
2596 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002597 script.AppendExtra(
2598 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002599 ' abort("E%d: %s partition has unexpected non-zero contents after '
2600 'OTA update");\n'
2601 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002602 else:
2603 script.Print('Verified the updated %s image.' % (partition,))
2604
Tianjie Xu209db462016-05-24 17:34:52 -07002605 if partition == "system":
2606 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2607 else:
2608 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2609
Tao Bao5fcaaef2015-06-01 13:40:49 -07002610 script.AppendExtra(
2611 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002612 ' abort("E%d: %s partition has unexpected contents after OTA '
2613 'update");\n'
2614 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002615
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002616 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002617 ZipWrite(output_zip,
2618 '{}.transfer.list'.format(self.path),
2619 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002620
Tao Bao76def242017-11-21 09:25:31 -08002621 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2622 # its size. Quailty 9 almost triples the compression time but doesn't
2623 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002624 # zip | brotli(quality 6) | brotli(quality 9)
2625 # compressed_size: 942M | 869M (~8% reduced) | 854M
2626 # compression_time: 75s | 265s | 719s
2627 # decompression_time: 15s | 25s | 25s
2628
2629 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002630 brotli_cmd = ['brotli', '--quality=6',
2631 '--output={}.new.dat.br'.format(self.path),
2632 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002633 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002634 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002635
2636 new_data_name = '{}.new.dat.br'.format(self.partition)
2637 ZipWrite(output_zip,
2638 '{}.new.dat.br'.format(self.path),
2639 new_data_name,
2640 compress_type=zipfile.ZIP_STORED)
2641 else:
2642 new_data_name = '{}.new.dat'.format(self.partition)
2643 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2644
Dan Albert8e0178d2015-01-27 15:53:15 -08002645 ZipWrite(output_zip,
2646 '{}.patch.dat'.format(self.path),
2647 '{}.patch.dat'.format(self.partition),
2648 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002649
Tianjie Xu209db462016-05-24 17:34:52 -07002650 if self.partition == "system":
2651 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2652 else:
2653 code = ErrorCode.VENDOR_UPDATE_FAILURE
2654
Yifan Hong10c530d2018-12-27 17:34:18 -08002655 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002656 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002657 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002658 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002659 device=self.device, partition=self.partition,
2660 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002661 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002662
Dan Albert8b72aef2015-03-23 19:13:21 -07002663 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002664 data = source.ReadRangeSet(ranges)
2665 ctx = sha1()
2666
2667 for p in data:
2668 ctx.update(p)
2669
2670 return ctx.hexdigest()
2671
Tao Baoe9b61912015-07-09 17:37:49 -07002672 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2673 """Return the hash value for all zero blocks."""
2674 zero_block = '\x00' * 4096
2675 ctx = sha1()
2676 for _ in range(num_blocks):
2677 ctx.update(zero_block)
2678
2679 return ctx.hexdigest()
2680
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002681
Tianjie Xu41976c72019-07-03 13:57:01 -07002682# Expose these two classes to support vendor-specific scripts
2683DataImage = images.DataImage
2684EmptyImage = images.EmptyImage
2685
Tao Bao76def242017-11-21 09:25:31 -08002686
Doug Zongker96a57e72010-09-26 14:57:41 -07002687# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002688PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002689 "ext4": "EMMC",
2690 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002691 "f2fs": "EMMC",
2692 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002693}
Doug Zongker96a57e72010-09-26 14:57:41 -07002694
Tao Bao76def242017-11-21 09:25:31 -08002695
Doug Zongker96a57e72010-09-26 14:57:41 -07002696def GetTypeAndDevice(mount_point, info):
2697 fstab = info["fstab"]
2698 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002699 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2700 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002701 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002702 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002703
2704
2705def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002706 """Parses and converts a PEM-encoded certificate into DER-encoded.
2707
2708 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2709
2710 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002711 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002712 """
2713 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002714 save = False
2715 for line in data.split("\n"):
2716 if "--END CERTIFICATE--" in line:
2717 break
2718 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002719 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002720 if "--BEGIN CERTIFICATE--" in line:
2721 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002722 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002723 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002724
Tao Bao04e1f012018-02-04 12:13:35 -08002725
2726def ExtractPublicKey(cert):
2727 """Extracts the public key (PEM-encoded) from the given certificate file.
2728
2729 Args:
2730 cert: The certificate filename.
2731
2732 Returns:
2733 The public key string.
2734
2735 Raises:
2736 AssertionError: On non-zero return from 'openssl'.
2737 """
2738 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2739 # While openssl 1.1 writes the key into the given filename followed by '-out',
2740 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2741 # stdout instead.
2742 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2743 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2744 pubkey, stderrdata = proc.communicate()
2745 assert proc.returncode == 0, \
2746 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2747 return pubkey
2748
2749
Tao Bao1ac886e2019-06-26 11:58:22 -07002750def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002751 """Extracts the AVB public key from the given public or private key.
2752
2753 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002754 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002755 key: The input key file, which should be PEM-encoded public or private key.
2756
2757 Returns:
2758 The path to the extracted AVB public key file.
2759 """
2760 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2761 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002762 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002763 return output
2764
2765
Doug Zongker412c02f2014-02-13 10:58:24 -08002766def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2767 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002768 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002769
Tao Bao6d5d6232018-03-09 17:04:42 -08002770 Most of the space in the boot and recovery images is just the kernel, which is
2771 identical for the two, so the resulting patch should be efficient. Add it to
2772 the output zip, along with a shell script that is run from init.rc on first
2773 boot to actually do the patching and install the new recovery image.
2774
2775 Args:
2776 input_dir: The top-level input directory of the target-files.zip.
2777 output_sink: The callback function that writes the result.
2778 recovery_img: File object for the recovery image.
2779 boot_img: File objects for the boot image.
2780 info_dict: A dict returned by common.LoadInfoDict() on the input
2781 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002782 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002783 if info_dict is None:
2784 info_dict = OPTIONS.info_dict
2785
Tao Bao6d5d6232018-03-09 17:04:42 -08002786 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002787 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2788
2789 if board_uses_vendorimage:
2790 # In this case, the output sink is rooted at VENDOR
2791 recovery_img_path = "etc/recovery.img"
2792 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2793 sh_dir = "bin"
2794 else:
2795 # In this case the output sink is rooted at SYSTEM
2796 recovery_img_path = "vendor/etc/recovery.img"
2797 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2798 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002799
Tao Baof2cffbd2015-07-22 12:33:18 -07002800 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002801 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002802
2803 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002804 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002805 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002806 # With system-root-image, boot and recovery images will have mismatching
2807 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2808 # to handle such a case.
2809 if system_root_image:
2810 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002811 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002812 assert not os.path.exists(path)
2813 else:
2814 diff_program = ["imgdiff"]
2815 if os.path.exists(path):
2816 diff_program.append("-b")
2817 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002818 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002819 else:
2820 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002821
2822 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2823 _, _, patch = d.ComputePatch()
2824 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002825
Dan Albertebb19aa2015-03-27 19:11:53 -07002826 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002827 # The following GetTypeAndDevice()s need to use the path in the target
2828 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002829 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2830 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2831 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002832 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002833
Tao Baof2cffbd2015-07-22 12:33:18 -07002834 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002835
2836 # Note that we use /vendor to refer to the recovery resources. This will
2837 # work for a separate vendor partition mounted at /vendor or a
2838 # /system/vendor subdirectory on the system partition, for which init will
2839 # create a symlink from /vendor to /system/vendor.
2840
2841 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002842if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2843 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002844 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002845 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2846 log -t recovery "Installing new recovery image: succeeded" || \\
2847 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002848else
2849 log -t recovery "Recovery image already installed"
2850fi
2851""" % {'type': recovery_type,
2852 'device': recovery_device,
2853 'sha1': recovery_img.sha1,
2854 'size': recovery_img.size}
2855 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002856 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002857if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2858 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002859 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002860 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2861 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2862 log -t recovery "Installing new recovery image: succeeded" || \\
2863 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002864else
2865 log -t recovery "Recovery image already installed"
2866fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002867""" % {'boot_size': boot_img.size,
2868 'boot_sha1': boot_img.sha1,
2869 'recovery_size': recovery_img.size,
2870 'recovery_sha1': recovery_img.sha1,
2871 'boot_type': boot_type,
2872 'boot_device': boot_device,
2873 'recovery_type': recovery_type,
2874 'recovery_device': recovery_device,
2875 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002876
Bill Peckhame868aec2019-09-17 17:06:47 -07002877 # The install script location moved from /system/etc to /system/bin in the L
2878 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2879 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002880
Tao Bao32fcdab2018-10-12 10:30:39 -07002881 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002882
Tao Baoda30cfa2017-12-01 16:19:46 -08002883 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002884
2885
2886class DynamicPartitionUpdate(object):
2887 def __init__(self, src_group=None, tgt_group=None, progress=None,
2888 block_difference=None):
2889 self.src_group = src_group
2890 self.tgt_group = tgt_group
2891 self.progress = progress
2892 self.block_difference = block_difference
2893
2894 @property
2895 def src_size(self):
2896 if not self.block_difference:
2897 return 0
2898 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2899
2900 @property
2901 def tgt_size(self):
2902 if not self.block_difference:
2903 return 0
2904 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2905
2906 @staticmethod
2907 def _GetSparseImageSize(img):
2908 if not img:
2909 return 0
2910 return img.blocksize * img.total_blocks
2911
2912
2913class DynamicGroupUpdate(object):
2914 def __init__(self, src_size=None, tgt_size=None):
2915 # None: group does not exist. 0: no size limits.
2916 self.src_size = src_size
2917 self.tgt_size = tgt_size
2918
2919
2920class DynamicPartitionsDifference(object):
2921 def __init__(self, info_dict, block_diffs, progress_dict=None,
2922 source_info_dict=None):
2923 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002924 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002925
2926 self._remove_all_before_apply = False
2927 if source_info_dict is None:
2928 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002929 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002930
Tao Baof1113e92019-06-18 12:10:14 -07002931 block_diff_dict = collections.OrderedDict(
2932 [(e.partition, e) for e in block_diffs])
2933
Yifan Hong10c530d2018-12-27 17:34:18 -08002934 assert len(block_diff_dict) == len(block_diffs), \
2935 "Duplicated BlockDifference object for {}".format(
2936 [partition for partition, count in
2937 collections.Counter(e.partition for e in block_diffs).items()
2938 if count > 1])
2939
Yifan Hong79997e52019-01-23 16:56:19 -08002940 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002941
2942 for p, block_diff in block_diff_dict.items():
2943 self._partition_updates[p] = DynamicPartitionUpdate()
2944 self._partition_updates[p].block_difference = block_diff
2945
2946 for p, progress in progress_dict.items():
2947 if p in self._partition_updates:
2948 self._partition_updates[p].progress = progress
2949
2950 tgt_groups = shlex.split(info_dict.get(
2951 "super_partition_groups", "").strip())
2952 src_groups = shlex.split(source_info_dict.get(
2953 "super_partition_groups", "").strip())
2954
2955 for g in tgt_groups:
2956 for p in shlex.split(info_dict.get(
2957 "super_%s_partition_list" % g, "").strip()):
2958 assert p in self._partition_updates, \
2959 "{} is in target super_{}_partition_list but no BlockDifference " \
2960 "object is provided.".format(p, g)
2961 self._partition_updates[p].tgt_group = g
2962
2963 for g in src_groups:
2964 for p in shlex.split(source_info_dict.get(
2965 "super_%s_partition_list" % g, "").strip()):
2966 assert p in self._partition_updates, \
2967 "{} is in source super_{}_partition_list but no BlockDifference " \
2968 "object is provided.".format(p, g)
2969 self._partition_updates[p].src_group = g
2970
Yifan Hong45433e42019-01-18 13:55:25 -08002971 target_dynamic_partitions = set(shlex.split(info_dict.get(
2972 "dynamic_partition_list", "").strip()))
2973 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2974 if u.tgt_size)
2975 assert block_diffs_with_target == target_dynamic_partitions, \
2976 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2977 list(target_dynamic_partitions), list(block_diffs_with_target))
2978
2979 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2980 "dynamic_partition_list", "").strip()))
2981 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2982 if u.src_size)
2983 assert block_diffs_with_source == source_dynamic_partitions, \
2984 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2985 list(source_dynamic_partitions), list(block_diffs_with_source))
2986
Yifan Hong10c530d2018-12-27 17:34:18 -08002987 if self._partition_updates:
2988 logger.info("Updating dynamic partitions %s",
2989 self._partition_updates.keys())
2990
Yifan Hong79997e52019-01-23 16:56:19 -08002991 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002992
2993 for g in tgt_groups:
2994 self._group_updates[g] = DynamicGroupUpdate()
2995 self._group_updates[g].tgt_size = int(info_dict.get(
2996 "super_%s_group_size" % g, "0").strip())
2997
2998 for g in src_groups:
2999 if g not in self._group_updates:
3000 self._group_updates[g] = DynamicGroupUpdate()
3001 self._group_updates[g].src_size = int(source_info_dict.get(
3002 "super_%s_group_size" % g, "0").strip())
3003
3004 self._Compute()
3005
3006 def WriteScript(self, script, output_zip, write_verify_script=False):
3007 script.Comment('--- Start patching dynamic partitions ---')
3008 for p, u in self._partition_updates.items():
3009 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3010 script.Comment('Patch partition %s' % p)
3011 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3012 write_verify_script=False)
3013
3014 op_list_path = MakeTempFile()
3015 with open(op_list_path, 'w') as f:
3016 for line in self._op_list:
3017 f.write('{}\n'.format(line))
3018
3019 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3020
3021 script.Comment('Update dynamic partition metadata')
3022 script.AppendExtra('assert(update_dynamic_partitions('
3023 'package_extract_file("dynamic_partitions_op_list")));')
3024
3025 if write_verify_script:
3026 for p, u in self._partition_updates.items():
3027 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3028 u.block_difference.WritePostInstallVerifyScript(script)
3029 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3030
3031 for p, u in self._partition_updates.items():
3032 if u.tgt_size and u.src_size <= u.tgt_size:
3033 script.Comment('Patch partition %s' % p)
3034 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3035 write_verify_script=write_verify_script)
3036 if write_verify_script:
3037 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3038
3039 script.Comment('--- End patching dynamic partitions ---')
3040
3041 def _Compute(self):
3042 self._op_list = list()
3043
3044 def append(line):
3045 self._op_list.append(line)
3046
3047 def comment(line):
3048 self._op_list.append("# %s" % line)
3049
3050 if self._remove_all_before_apply:
3051 comment('Remove all existing dynamic partitions and groups before '
3052 'applying full OTA')
3053 append('remove_all_groups')
3054
3055 for p, u in self._partition_updates.items():
3056 if u.src_group and not u.tgt_group:
3057 append('remove %s' % p)
3058
3059 for p, u in self._partition_updates.items():
3060 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3061 comment('Move partition %s from %s to default' % (p, u.src_group))
3062 append('move %s default' % p)
3063
3064 for p, u in self._partition_updates.items():
3065 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3066 comment('Shrink partition %s from %d to %d' %
3067 (p, u.src_size, u.tgt_size))
3068 append('resize %s %s' % (p, u.tgt_size))
3069
3070 for g, u in self._group_updates.items():
3071 if u.src_size is not None and u.tgt_size is None:
3072 append('remove_group %s' % g)
3073 if (u.src_size is not None and u.tgt_size is not None and
3074 u.src_size > u.tgt_size):
3075 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3076 append('resize_group %s %d' % (g, u.tgt_size))
3077
3078 for g, u in self._group_updates.items():
3079 if u.src_size is None and u.tgt_size is not None:
3080 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3081 append('add_group %s %d' % (g, u.tgt_size))
3082 if (u.src_size is not None and u.tgt_size is not None and
3083 u.src_size < u.tgt_size):
3084 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3085 append('resize_group %s %d' % (g, u.tgt_size))
3086
3087 for p, u in self._partition_updates.items():
3088 if u.tgt_group and not u.src_group:
3089 comment('Add partition %s to group %s' % (p, u.tgt_group))
3090 append('add %s %s' % (p, u.tgt_group))
3091
3092 for p, u in self._partition_updates.items():
3093 if u.tgt_size and u.src_size < u.tgt_size:
3094 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3095 append('resize %s %d' % (p, u.tgt_size))
3096
3097 for p, u in self._partition_updates.items():
3098 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3099 comment('Move partition %s from default to %s' %
3100 (p, u.tgt_group))
3101 append('move %s %s' % (p, u.tgt_group))