blob: 54ee7cf918a5e4ea382887cca764a121eb0ae404 [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()
826 return merged_dict
827
828
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800829def AppendAVBSigningArgs(cmd, partition):
830 """Append signing arguments for avbtool."""
831 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
832 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
Daniel Mentz25478182019-08-21 18:09:46 -0700833 if key_path and not os.path.exists(key_path) and OPTIONS.search_path:
834 new_key_path = os.path.join(OPTIONS.search_path, key_path)
835 if os.path.exists(new_key_path):
836 key_path = new_key_path
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800837 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
838 if key_path and algorithm:
839 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700840 avb_salt = OPTIONS.info_dict.get("avb_salt")
841 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700842 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700843 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800844
845
Tao Bao765668f2019-10-04 22:03:00 -0700846def GetAvbPartitionArg(partition, image, info_dict=None):
Daniel Norman276f0622019-07-26 14:13:51 -0700847 """Returns the VBMeta arguments for partition.
848
849 It sets up the VBMeta argument by including the partition descriptor from the
850 given 'image', or by configuring the partition as a chained partition.
851
852 Args:
853 partition: The name of the partition (e.g. "system").
854 image: The path to the partition image.
855 info_dict: A dict returned by common.LoadInfoDict(). Will use
856 OPTIONS.info_dict if None has been given.
857
858 Returns:
859 A list of VBMeta arguments.
860 """
861 if info_dict is None:
862 info_dict = OPTIONS.info_dict
863
864 # Check if chain partition is used.
865 key_path = info_dict.get("avb_" + partition + "_key_path")
cfig1aeef722019-09-20 22:45:06 +0800866 if not key_path:
867 return ["--include_descriptors_from_image", image]
868
869 # For a non-A/B device, we don't chain /recovery nor include its descriptor
870 # into vbmeta.img. The recovery image will be configured on an independent
871 # boot chain, to be verified with AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION.
872 # See details at
873 # https://android.googlesource.com/platform/external/avb/+/master/README.md#booting-into-recovery.
Tao Bao3612c882019-10-14 17:49:31 -0700874 if info_dict.get("ab_update") != "true" and partition == "recovery":
cfig1aeef722019-09-20 22:45:06 +0800875 return []
876
877 # Otherwise chain the partition into vbmeta.
878 chained_partition_arg = GetAvbChainedPartitionArg(partition, info_dict)
879 return ["--chain_partition", chained_partition_arg]
Daniel Norman276f0622019-07-26 14:13:51 -0700880
881
Tao Bao02a08592018-07-22 12:40:45 -0700882def GetAvbChainedPartitionArg(partition, info_dict, key=None):
883 """Constructs and returns the arg to build or verify a chained partition.
884
885 Args:
886 partition: The partition name.
887 info_dict: The info dict to look up the key info and rollback index
888 location.
889 key: The key to be used for building or verifying the partition. Defaults to
890 the key listed in info_dict.
891
892 Returns:
893 A string of form "partition:rollback_index_location:key" that can be used to
894 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700895 """
896 if key is None:
897 key = info_dict["avb_" + partition + "_key_path"]
Daniel Mentz25478182019-08-21 18:09:46 -0700898 if key and not os.path.exists(key) and OPTIONS.search_path:
899 new_key_path = os.path.join(OPTIONS.search_path, key)
900 if os.path.exists(new_key_path):
901 key = new_key_path
Tao Bao1ac886e2019-06-26 11:58:22 -0700902 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700903 rollback_index_location = info_dict[
904 "avb_" + partition + "_rollback_index_location"]
905 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
906
907
Daniel Norman276f0622019-07-26 14:13:51 -0700908def BuildVBMeta(image_path, partitions, name, needed_partitions):
909 """Creates a VBMeta image.
910
911 It generates the requested VBMeta image. The requested image could be for
912 top-level or chained VBMeta image, which is determined based on the name.
913
914 Args:
915 image_path: The output path for the new VBMeta image.
916 partitions: A dict that's keyed by partition names with image paths as
917 values. Only valid partition names are accepted, as listed in
918 common.AVB_PARTITIONS.
919 name: Name of the VBMeta partition, e.g. 'vbmeta', 'vbmeta_system'.
920 needed_partitions: Partitions whose descriptors should be included into the
921 generated VBMeta image.
922
923 Raises:
924 AssertionError: On invalid input args.
925 """
926 avbtool = OPTIONS.info_dict["avb_avbtool"]
927 cmd = [avbtool, "make_vbmeta_image", "--output", image_path]
928 AppendAVBSigningArgs(cmd, name)
929
930 for partition, path in partitions.items():
931 if partition not in needed_partitions:
932 continue
933 assert (partition in AVB_PARTITIONS or
934 partition in AVB_VBMETA_PARTITIONS), \
935 'Unknown partition: {}'.format(partition)
936 assert os.path.exists(path), \
937 'Failed to find {} for {}'.format(path, partition)
938 cmd.extend(GetAvbPartitionArg(partition, path))
939
940 args = OPTIONS.info_dict.get("avb_{}_args".format(name))
941 if args and args.strip():
942 split_args = shlex.split(args)
943 for index, arg in enumerate(split_args[:-1]):
944 # Sanity check that the image file exists. Some images might be defined
945 # as a path relative to source tree, which may not be available at the
946 # same location when running this script (we have the input target_files
947 # zip only). For such cases, we additionally scan other locations (e.g.
948 # IMAGES/, RADIO/, etc) before bailing out.
949 if arg == '--include_descriptors_from_image':
950 image_path = split_args[index + 1]
951 if os.path.exists(image_path):
952 continue
953 found = False
954 for dir_name in ['IMAGES', 'RADIO', 'PREBUILT_IMAGES']:
955 alt_path = os.path.join(
956 OPTIONS.input_tmp, dir_name, os.path.basename(image_path))
957 if os.path.exists(alt_path):
958 split_args[index + 1] = alt_path
959 found = True
960 break
961 assert found, 'Failed to find {}'.format(image_path)
962 cmd.extend(split_args)
963
964 RunAndCheckOutput(cmd)
965
Dan Austin52903642019-12-12 15:44:00 -0800966 if OPTIONS.aftl_server is not None:
967 # Ensure the other AFTL parameters are set as well.
968 assert OPTIONS.aftl_key_path is not None, 'No AFTL key provided.'
969 assert OPTIONS.aftl_manufacturer_key_path is not None, 'No AFTL manufacturer key provided.'
970 assert OPTIONS.aftl_signer_helper is not None, 'No AFTL signer helper provided.'
971 # AFTL inclusion proof generation code will go here.
Daniel Norman276f0622019-07-26 14:13:51 -0700972
Steve Mucklee1b10862019-07-10 10:49:37 -0700973def _MakeRamdisk(sourcedir, fs_config_file=None):
974 ramdisk_img = tempfile.NamedTemporaryFile()
975
976 if fs_config_file is not None and os.access(fs_config_file, os.F_OK):
977 cmd = ["mkbootfs", "-f", fs_config_file,
978 os.path.join(sourcedir, "RAMDISK")]
979 else:
980 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
981 p1 = Run(cmd, stdout=subprocess.PIPE)
982 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
983
984 p2.wait()
985 p1.wait()
986 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
987 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
988
989 return ramdisk_img
990
991
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700992def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800993 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700994 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700995
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700996 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800997 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
998 we are building a two-step special image (i.e. building a recovery image to
999 be loaded into /boot in two-step OTAs).
1000
1001 Return the image data, or None if sourcedir does not appear to contains files
1002 for building the requested image.
1003 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001004
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001005 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
1006 return None
1007
1008 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -07001009 return None
Doug Zongkereef39442009-04-02 12:14:19 -07001010
Doug Zongkerd5131602012-08-02 14:46:42 -07001011 if info_dict is None:
1012 info_dict = OPTIONS.info_dict
1013
Doug Zongkereef39442009-04-02 12:14:19 -07001014 img = tempfile.NamedTemporaryFile()
1015
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001016 if has_ramdisk:
Steve Mucklee1b10862019-07-10 10:49:37 -07001017 ramdisk_img = _MakeRamdisk(sourcedir, fs_config_file)
Doug Zongkereef39442009-04-02 12:14:19 -07001018
Bjorn Andersson612e2cd2012-11-25 16:53:44 -08001019 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1020 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1021
1022 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -07001023
Benoit Fradina45a8682014-07-14 21:00:43 +02001024 fn = os.path.join(sourcedir, "second")
1025 if os.access(fn, os.F_OK):
1026 cmd.append("--second")
1027 cmd.append(fn)
1028
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -08001029 fn = os.path.join(sourcedir, "dtb")
1030 if os.access(fn, os.F_OK):
1031 cmd.append("--dtb")
1032 cmd.append(fn)
1033
Doug Zongker171f1cd2009-06-15 22:36:37 -07001034 fn = os.path.join(sourcedir, "cmdline")
1035 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -07001036 cmd.append("--cmdline")
1037 cmd.append(open(fn).read().rstrip("\n"))
1038
1039 fn = os.path.join(sourcedir, "base")
1040 if os.access(fn, os.F_OK):
1041 cmd.append("--base")
1042 cmd.append(open(fn).read().rstrip("\n"))
1043
Ying Wang4de6b5b2010-08-25 14:29:34 -07001044 fn = os.path.join(sourcedir, "pagesize")
1045 if os.access(fn, os.F_OK):
1046 cmd.append("--pagesize")
1047 cmd.append(open(fn).read().rstrip("\n"))
1048
Tao Bao76def242017-11-21 09:25:31 -08001049 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -07001050 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -07001051 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -07001052
Tao Bao76def242017-11-21 09:25:31 -08001053 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +00001054 if args and args.strip():
1055 cmd.extend(shlex.split(args))
1056
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001057 if has_ramdisk:
1058 cmd.extend(["--ramdisk", ramdisk_img.name])
1059
Tao Baod95e9fd2015-03-29 23:07:41 -07001060 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -08001061 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -07001062 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001063 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -07001064 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001065 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -07001066
Tao Baobf70c3182017-07-11 17:27:55 -07001067 # "boot" or "recovery", without extension.
1068 partition_name = os.path.basename(sourcedir).lower()
1069
Chen, ZhiminX752439b2018-09-23 22:10:47 +08001070 if partition_name == "recovery":
1071 if info_dict.get("include_recovery_dtbo") == "true":
1072 fn = os.path.join(sourcedir, "recovery_dtbo")
1073 cmd.extend(["--recovery_dtbo", fn])
1074 if info_dict.get("include_recovery_acpio") == "true":
1075 fn = os.path.join(sourcedir, "recovery_acpio")
1076 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -07001077
Tao Bao986ee862018-10-04 15:46:16 -07001078 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -07001079
Tao Bao76def242017-11-21 09:25:31 -08001080 if (info_dict.get("boot_signer") == "true" and
1081 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -08001082 # Hard-code the path as "/boot" for two-step special recovery image (which
1083 # will be loaded into /boot during the two-step OTA).
1084 if two_step_image:
1085 path = "/boot"
1086 else:
Tao Baobf70c3182017-07-11 17:27:55 -07001087 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -07001088 cmd = [OPTIONS.boot_signer_path]
1089 cmd.extend(OPTIONS.boot_signer_args)
1090 cmd.extend([path, img.name,
1091 info_dict["verity_key"] + ".pk8",
1092 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -07001093 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -07001094
Tao Baod95e9fd2015-03-29 23:07:41 -07001095 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -08001096 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -07001097 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -07001098 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -08001099 # We have switched from the prebuilt futility binary to using the tool
1100 # (futility-host) built from the source. Override the setting in the old
1101 # TF.zip.
1102 futility = info_dict["futility"]
1103 if futility.startswith("prebuilts/"):
1104 futility = "futility-host"
1105 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -07001106 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -07001107 info_dict["vboot_key"] + ".vbprivk",
1108 info_dict["vboot_subkey"] + ".vbprivk",
1109 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -07001110 img.name]
Tao Bao986ee862018-10-04 15:46:16 -07001111 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -07001112
Tao Baof3282b42015-04-01 11:21:55 -07001113 # Clean up the temp files.
1114 img_unsigned.close()
1115 img_keyblock.close()
1116
David Zeuthen8fecb282017-12-01 16:24:01 -05001117 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +08001118 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -07001119 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -05001120 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001121 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -07001122 "--partition_size", str(part_size), "--partition_name",
1123 partition_name]
1124 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -05001125 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001126 if args and args.strip():
1127 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -07001128 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -05001129
1130 img.seek(os.SEEK_SET, 0)
1131 data = img.read()
1132
1133 if has_ramdisk:
1134 ramdisk_img.close()
1135 img.close()
1136
1137 return data
1138
1139
Doug Zongkerd5131602012-08-02 14:46:42 -07001140def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -08001141 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001142 """Return a File object with the desired bootable image.
1143
1144 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
1145 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1146 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -07001147
Doug Zongker55d93282011-01-25 17:03:34 -08001148 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
1149 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001150 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -08001151 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001152
1153 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1154 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -07001155 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001156 return File.FromLocalFile(name, prebuilt_path)
1157
Tao Bao32fcdab2018-10-12 10:30:39 -07001158 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001159
1160 if info_dict is None:
1161 info_dict = OPTIONS.info_dict
1162
1163 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -08001164 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
1165 # for recovery.
1166 has_ramdisk = (info_dict.get("system_root_image") != "true" or
1167 prebuilt_name != "boot.img" or
1168 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -07001169
Doug Zongker6f1d0312014-08-22 08:07:12 -07001170 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -04001171 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
1172 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -08001173 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -07001174 if data:
1175 return File(name, data)
1176 return None
Doug Zongker55d93282011-01-25 17:03:34 -08001177
Doug Zongkereef39442009-04-02 12:14:19 -07001178
Steve Mucklee1b10862019-07-10 10:49:37 -07001179def _BuildVendorBootImage(sourcedir, info_dict=None):
1180 """Build a vendor boot image from the specified sourcedir.
1181
1182 Take a ramdisk, dtb, and vendor_cmdline from the input (in 'sourcedir'), and
1183 turn them into a vendor boot image.
1184
1185 Return the image data, or None if sourcedir does not appear to contains files
1186 for building the requested image.
1187 """
1188
1189 if info_dict is None:
1190 info_dict = OPTIONS.info_dict
1191
1192 img = tempfile.NamedTemporaryFile()
1193
1194 ramdisk_img = _MakeRamdisk(sourcedir)
1195
1196 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
1197 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
1198
1199 cmd = [mkbootimg]
1200
1201 fn = os.path.join(sourcedir, "dtb")
1202 if os.access(fn, os.F_OK):
1203 cmd.append("--dtb")
1204 cmd.append(fn)
1205
1206 fn = os.path.join(sourcedir, "vendor_cmdline")
1207 if os.access(fn, os.F_OK):
1208 cmd.append("--vendor_cmdline")
1209 cmd.append(open(fn).read().rstrip("\n"))
1210
1211 fn = os.path.join(sourcedir, "base")
1212 if os.access(fn, os.F_OK):
1213 cmd.append("--base")
1214 cmd.append(open(fn).read().rstrip("\n"))
1215
1216 fn = os.path.join(sourcedir, "pagesize")
1217 if os.access(fn, os.F_OK):
1218 cmd.append("--pagesize")
1219 cmd.append(open(fn).read().rstrip("\n"))
1220
1221 args = info_dict.get("mkbootimg_args")
1222 if args and args.strip():
1223 cmd.extend(shlex.split(args))
1224
1225 args = info_dict.get("mkbootimg_version_args")
1226 if args and args.strip():
1227 cmd.extend(shlex.split(args))
1228
1229 cmd.extend(["--vendor_ramdisk", ramdisk_img.name])
1230 cmd.extend(["--vendor_boot", img.name])
1231
1232 RunAndCheckOutput(cmd)
1233
1234 # AVB: if enabled, calculate and add hash.
1235 if info_dict.get("avb_enable") == "true":
1236 avbtool = info_dict["avb_avbtool"]
1237 part_size = info_dict["vendor_boot_size"]
1238 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Donghoon Yu92420db2019-11-21 14:20:17 +09001239 "--partition_size", str(part_size), "--partition_name", "vendor_boot"]
Steve Mucklee1b10862019-07-10 10:49:37 -07001240 AppendAVBSigningArgs(cmd, "vendor_boot")
1241 args = info_dict.get("avb_vendor_boot_add_hash_footer_args")
1242 if args and args.strip():
1243 cmd.extend(shlex.split(args))
1244 RunAndCheckOutput(cmd)
1245
1246 img.seek(os.SEEK_SET, 0)
1247 data = img.read()
1248
1249 ramdisk_img.close()
1250 img.close()
1251
1252 return data
1253
1254
1255def GetVendorBootImage(name, prebuilt_name, unpack_dir, tree_subdir,
1256 info_dict=None):
1257 """Return a File object with the desired vendor boot image.
1258
1259 Look for it under 'unpack_dir'/IMAGES, otherwise construct it from
1260 the source files in 'unpack_dir'/'tree_subdir'."""
1261
1262 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
1263 if os.path.exists(prebuilt_path):
1264 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
1265 return File.FromLocalFile(name, prebuilt_path)
1266
1267 logger.info("building image from target_files %s...", tree_subdir)
1268
1269 if info_dict is None:
1270 info_dict = OPTIONS.info_dict
1271
1272 data = _BuildVendorBootImage(os.path.join(unpack_dir, tree_subdir), info_dict)
1273 if data:
1274 return File(name, data)
1275 return None
1276
1277
Narayan Kamatha07bf042017-08-14 14:49:21 +01001278def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -08001279 """Gunzips the given gzip compressed file to a given output file."""
1280 with gzip.open(in_filename, "rb") as in_file, \
1281 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +01001282 shutil.copyfileobj(in_file, out_file)
1283
1284
Tao Bao0ff15de2019-03-20 11:26:06 -07001285def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001286 """Unzips the archive to the given directory.
1287
1288 Args:
1289 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001290 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -07001291 patterns: Files to unzip from the archive. If omitted, will unzip the entire
1292 archvie. Non-matching patterns will be filtered out. If there's no match
1293 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001294 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001295 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -07001296 if patterns is not None:
1297 # Filter out non-matching patterns. unzip will complain otherwise.
1298 with zipfile.ZipFile(filename) as input_zip:
1299 names = input_zip.namelist()
1300 filtered = [
1301 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
1302
1303 # There isn't any matching files. Don't unzip anything.
1304 if not filtered:
1305 return
1306 cmd.extend(filtered)
1307
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001308 RunAndCheckOutput(cmd)
1309
1310
Doug Zongker75f17362009-12-08 13:46:44 -08001311def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -08001312 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -08001313
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001314 Args:
1315 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
1316 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
1317
1318 pattern: Files to unzip from the archive. If omitted, will unzip the entire
1319 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -08001320
Tao Bao1c830bf2017-12-25 10:43:47 -08001321 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -08001322 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -08001323 """
Doug Zongkereef39442009-04-02 12:14:19 -07001324
Tao Bao1c830bf2017-12-25 10:43:47 -08001325 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -08001326 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
1327 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001328 UnzipToDir(m.group(1), tmp, pattern)
1329 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001330 filename = m.group(1)
1331 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -08001332 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -08001333
Tao Baodba59ee2018-01-09 13:21:02 -08001334 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -07001335
1336
Yifan Hong8a66a712019-04-04 15:37:57 -07001337def GetUserImage(which, tmpdir, input_zip,
1338 info_dict=None,
1339 allow_shared_blocks=None,
1340 hashtree_info_generator=None,
1341 reset_file_map=False):
1342 """Returns an Image object suitable for passing to BlockImageDiff.
1343
1344 This function loads the specified image from the given path. If the specified
1345 image is sparse, it also performs additional processing for OTA purpose. For
1346 example, it always adds block 0 to clobbered blocks list. It also detects
1347 files that cannot be reconstructed from the block list, for whom we should
1348 avoid applying imgdiff.
1349
1350 Args:
1351 which: The partition name.
1352 tmpdir: The directory that contains the prebuilt image and block map file.
1353 input_zip: The target-files ZIP archive.
1354 info_dict: The dict to be looked up for relevant info.
1355 allow_shared_blocks: If image is sparse, whether having shared blocks is
1356 allowed. If none, it is looked up from info_dict.
1357 hashtree_info_generator: If present and image is sparse, generates the
1358 hashtree_info for this sparse image.
1359 reset_file_map: If true and image is sparse, reset file map before returning
1360 the image.
1361 Returns:
1362 A Image object. If it is a sparse image and reset_file_map is False, the
1363 image will have file_map info loaded.
1364 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001365 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -07001366 info_dict = LoadInfoDict(input_zip)
1367
1368 is_sparse = info_dict.get("extfs_sparse_flag")
1369
1370 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
1371 # shared blocks (i.e. some blocks will show up in multiple files' block
1372 # list). We can only allocate such shared blocks to the first "owner", and
1373 # disable imgdiff for all later occurrences.
1374 if allow_shared_blocks is None:
1375 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
1376
1377 if is_sparse:
1378 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1379 hashtree_info_generator)
1380 if reset_file_map:
1381 img.ResetFileMap()
1382 return img
1383 else:
1384 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
1385
1386
1387def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
1388 """Returns a Image object suitable for passing to BlockImageDiff.
1389
1390 This function loads the specified non-sparse image from the given path.
1391
1392 Args:
1393 which: The partition name.
1394 tmpdir: The directory that contains the prebuilt image and block map file.
1395 Returns:
1396 A Image object.
1397 """
1398 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1399 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1400
1401 # The image and map files must have been created prior to calling
1402 # ota_from_target_files.py (since LMP).
1403 assert os.path.exists(path) and os.path.exists(mappath)
1404
Tianjie Xu41976c72019-07-03 13:57:01 -07001405 return images.FileImage(path, hashtree_info_generator=hashtree_info_generator)
1406
Yifan Hong8a66a712019-04-04 15:37:57 -07001407
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001408def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
1409 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -08001410 """Returns a SparseImage object suitable for passing to BlockImageDiff.
1411
1412 This function loads the specified sparse image from the given path, and
1413 performs additional processing for OTA purpose. For example, it always adds
1414 block 0 to clobbered blocks list. It also detects files that cannot be
1415 reconstructed from the block list, for whom we should avoid applying imgdiff.
1416
1417 Args:
Tao Baob2de7d92019-04-10 10:01:47 -07001418 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -08001419 tmpdir: The directory that contains the prebuilt image and block map file.
1420 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -08001421 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001422 hashtree_info_generator: If present, generates the hashtree_info for this
1423 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -08001424 Returns:
1425 A SparseImage object, with file_map info loaded.
1426 """
Tao Baoc765cca2018-01-31 17:32:40 -08001427 path = os.path.join(tmpdir, "IMAGES", which + ".img")
1428 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
1429
1430 # The image and map files must have been created prior to calling
1431 # ota_from_target_files.py (since LMP).
1432 assert os.path.exists(path) and os.path.exists(mappath)
1433
1434 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
1435 # it to clobbered_blocks so that it will be written to the target
1436 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
1437 clobbered_blocks = "0"
1438
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001439 image = sparse_img.SparseImage(
1440 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
1441 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -08001442
1443 # block.map may contain less blocks, because mke2fs may skip allocating blocks
1444 # if they contain all zeros. We can't reconstruct such a file from its block
1445 # list. Tag such entries accordingly. (Bug: 65213616)
1446 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -08001447 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -07001448 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -08001449 continue
1450
Tom Cherryd14b8952018-08-09 14:26:00 -07001451 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
1452 # filename listed in system.map may contain an additional leading slash
1453 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
1454 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -08001455 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -07001456
Tom Cherryd14b8952018-08-09 14:26:00 -07001457 # Special handling another case, where files not under /system
1458 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -07001459 if which == 'system' and not arcname.startswith('SYSTEM'):
1460 arcname = 'ROOT/' + arcname
1461
1462 assert arcname in input_zip.namelist(), \
1463 "Failed to find the ZIP entry for {}".format(entry)
1464
Tao Baoc765cca2018-01-31 17:32:40 -08001465 info = input_zip.getinfo(arcname)
1466 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -08001467
1468 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -08001469 # image, check the original block list to determine its completeness. Note
1470 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -08001471 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -08001472 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -08001473
Tao Baoc765cca2018-01-31 17:32:40 -08001474 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
1475 ranges.extra['incomplete'] = True
1476
1477 return image
1478
1479
Doug Zongkereef39442009-04-02 12:14:19 -07001480def GetKeyPasswords(keylist):
1481 """Given a list of keys, prompt the user to enter passwords for
1482 those which require them. Return a {key: password} dict. password
1483 will be None if the key has no password."""
1484
Doug Zongker8ce7c252009-05-22 13:34:54 -07001485 no_passwords = []
1486 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001487 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001488 devnull = open("/dev/null", "w+b")
1489 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001490 # We don't need a password for things that aren't really keys.
1491 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001492 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001493 continue
1494
T.R. Fullhart37e10522013-03-18 10:31:26 -07001495 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001496 "-inform", "DER", "-nocrypt"],
1497 stdin=devnull.fileno(),
1498 stdout=devnull.fileno(),
1499 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001500 p.communicate()
1501 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001502 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001503 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001504 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001505 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1506 "-inform", "DER", "-passin", "pass:"],
1507 stdin=devnull.fileno(),
1508 stdout=devnull.fileno(),
1509 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001510 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001511 if p.returncode == 0:
1512 # Encrypted key with empty string as password.
1513 key_passwords[k] = ''
1514 elif stderr.startswith('Error decrypting key'):
1515 # Definitely encrypted key.
1516 # It would have said "Error reading key" if it didn't parse correctly.
1517 need_passwords.append(k)
1518 else:
1519 # Potentially, a type of key that openssl doesn't understand.
1520 # We'll let the routines in signapk.jar handle it.
1521 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001522 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001523
T.R. Fullhart37e10522013-03-18 10:31:26 -07001524 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001525 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001526 return key_passwords
1527
1528
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001529def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001530 """Gets the minSdkVersion declared in the APK.
1531
changho.shin0f125362019-07-08 10:59:00 +09001532 It calls 'aapt2' to query the embedded minSdkVersion from the given APK file.
Tao Baof47bf0f2018-03-21 23:28:51 -07001533 This can be both a decimal number (API Level) or a codename.
1534
1535 Args:
1536 apk_name: The APK filename.
1537
1538 Returns:
1539 The parsed SDK version string.
1540
1541 Raises:
1542 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001543 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001544 proc = Run(
changho.shin0f125362019-07-08 10:59:00 +09001545 ["aapt2", "dump", "badging", apk_name], stdout=subprocess.PIPE,
Tao Baof47bf0f2018-03-21 23:28:51 -07001546 stderr=subprocess.PIPE)
1547 stdoutdata, stderrdata = proc.communicate()
1548 if proc.returncode != 0:
1549 raise ExternalError(
changho.shin0f125362019-07-08 10:59:00 +09001550 "Failed to obtain minSdkVersion: aapt2 return code {}:\n{}\n{}".format(
Tao Baof47bf0f2018-03-21 23:28:51 -07001551 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001552
Tao Baof47bf0f2018-03-21 23:28:51 -07001553 for line in stdoutdata.split("\n"):
1554 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001555 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1556 if m:
1557 return m.group(1)
changho.shin0f125362019-07-08 10:59:00 +09001558 raise ExternalError("No minSdkVersion returned by aapt2")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001559
1560
1561def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001562 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001563
Tao Baof47bf0f2018-03-21 23:28:51 -07001564 If minSdkVersion is set to a codename, it is translated to a number using the
1565 provided map.
1566
1567 Args:
1568 apk_name: The APK filename.
1569
1570 Returns:
1571 The parsed SDK version number.
1572
1573 Raises:
1574 ExternalError: On failing to get the min SDK version number.
1575 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001576 version = GetMinSdkVersion(apk_name)
1577 try:
1578 return int(version)
1579 except ValueError:
1580 # Not a decimal number. Codename?
1581 if version in codename_to_api_level_map:
1582 return codename_to_api_level_map[version]
1583 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001584 raise ExternalError(
1585 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1586 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001587
1588
1589def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001590 codename_to_api_level_map=None, whole_file=False,
1591 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001592 """Sign the input_name zip/jar/apk, producing output_name. Use the
1593 given key and password (the latter may be None if the key does not
1594 have a password.
1595
Doug Zongker951495f2009-08-14 12:44:19 -07001596 If whole_file is true, use the "-w" option to SignApk to embed a
1597 signature that covers the whole file in the archive comment of the
1598 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001599
1600 min_api_level is the API Level (int) of the oldest platform this file may end
1601 up on. If not specified for an APK, the API Level is obtained by interpreting
1602 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1603
1604 codename_to_api_level_map is needed to translate the codename which may be
1605 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001606
1607 Caller may optionally specify extra args to be passed to SignApk, which
1608 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001609 """
Tao Bao76def242017-11-21 09:25:31 -08001610 if codename_to_api_level_map is None:
1611 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001612 if extra_signapk_args is None:
1613 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001614
Alex Klyubin9667b182015-12-10 13:38:50 -08001615 java_library_path = os.path.join(
1616 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1617
Tao Baoe95540e2016-11-08 12:08:53 -08001618 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1619 ["-Djava.library.path=" + java_library_path,
1620 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001621 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001622 if whole_file:
1623 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001624
1625 min_sdk_version = min_api_level
1626 if min_sdk_version is None:
1627 if not whole_file:
1628 min_sdk_version = GetMinSdkVersionInt(
1629 input_name, codename_to_api_level_map)
1630 if min_sdk_version is not None:
1631 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1632
T.R. Fullhart37e10522013-03-18 10:31:26 -07001633 cmd.extend([key + OPTIONS.public_key_suffix,
1634 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001635 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001636
Tao Bao73dd4f42018-10-04 16:25:33 -07001637 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001638 if password is not None:
1639 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001640 stdoutdata, _ = proc.communicate(password)
1641 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001642 raise ExternalError(
1643 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001644 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001645
Doug Zongkereef39442009-04-02 12:14:19 -07001646
Doug Zongker37974732010-09-16 17:44:38 -07001647def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001648 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001649
Tao Bao9dd909e2017-11-14 11:27:32 -08001650 For non-AVB images, raise exception if the data is too big. Print a warning
1651 if the data is nearing the maximum size.
1652
1653 For AVB images, the actual image size should be identical to the limit.
1654
1655 Args:
1656 data: A string that contains all the data for the partition.
1657 target: The partition name. The ".img" suffix is optional.
1658 info_dict: The dict to be looked up for relevant info.
1659 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001660 if target.endswith(".img"):
1661 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001662 mount_point = "/" + target
1663
Ying Wangf8824af2014-06-03 14:07:27 -07001664 fs_type = None
1665 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001666 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001667 if mount_point == "/userdata":
1668 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001669 p = info_dict["fstab"][mount_point]
1670 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001671 device = p.device
1672 if "/" in device:
1673 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001674 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001675 if not fs_type or not limit:
1676 return
Doug Zongkereef39442009-04-02 12:14:19 -07001677
Andrew Boie0f9aec82012-02-14 09:32:52 -08001678 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001679 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1680 # path.
1681 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1682 if size != limit:
1683 raise ExternalError(
1684 "Mismatching image size for %s: expected %d actual %d" % (
1685 target, limit, size))
1686 else:
1687 pct = float(size) * 100.0 / limit
1688 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1689 if pct >= 99.0:
1690 raise ExternalError(msg)
1691 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001692 logger.warning("\n WARNING: %s\n", msg)
1693 else:
1694 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001695
1696
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001697def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001698 """Parses the APK certs info from a given target-files zip.
1699
1700 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1701 tuple with the following elements: (1) a dictionary that maps packages to
1702 certs (based on the "certificate" and "private_key" attributes in the file;
1703 (2) a string representing the extension of compressed APKs in the target files
1704 (e.g ".gz", ".bro").
1705
1706 Args:
1707 tf_zip: The input target_files ZipFile (already open).
1708
1709 Returns:
1710 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1711 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1712 no compressed APKs.
1713 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001714 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001715 compressed_extension = None
1716
Tao Bao0f990332017-09-08 19:02:54 -07001717 # META/apkcerts.txt contains the info for _all_ the packages known at build
1718 # time. Filter out the ones that are not installed.
1719 installed_files = set()
1720 for name in tf_zip.namelist():
1721 basename = os.path.basename(name)
1722 if basename:
1723 installed_files.add(basename)
1724
Tao Baoda30cfa2017-12-01 16:19:46 -08001725 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001726 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001727 if not line:
1728 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001729 m = re.match(
1730 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1731 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1732 line)
1733 if not m:
1734 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001735
Tao Bao818ddf52018-01-05 11:17:34 -08001736 matches = m.groupdict()
1737 cert = matches["CERT"]
1738 privkey = matches["PRIVKEY"]
1739 name = matches["NAME"]
1740 this_compressed_extension = matches["COMPRESSED"]
1741
1742 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1743 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1744 if cert in SPECIAL_CERT_STRINGS and not privkey:
1745 certmap[name] = cert
1746 elif (cert.endswith(OPTIONS.public_key_suffix) and
1747 privkey.endswith(OPTIONS.private_key_suffix) and
1748 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1749 certmap[name] = cert[:-public_key_suffix_len]
1750 else:
1751 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1752
1753 if not this_compressed_extension:
1754 continue
1755
1756 # Only count the installed files.
1757 filename = name + '.' + this_compressed_extension
1758 if filename not in installed_files:
1759 continue
1760
1761 # Make sure that all the values in the compression map have the same
1762 # extension. We don't support multiple compression methods in the same
1763 # system image.
1764 if compressed_extension:
1765 if this_compressed_extension != compressed_extension:
1766 raise ValueError(
1767 "Multiple compressed extensions: {} vs {}".format(
1768 compressed_extension, this_compressed_extension))
1769 else:
1770 compressed_extension = this_compressed_extension
1771
1772 return (certmap,
1773 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001774
1775
Doug Zongkereef39442009-04-02 12:14:19 -07001776COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001777Global options
1778
1779 -p (--path) <dir>
1780 Prepend <dir>/bin to the list of places to search for binaries run by this
1781 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001782
Doug Zongker05d3dea2009-06-22 11:32:31 -07001783 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001784 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001785
Tao Bao30df8b42018-04-23 15:32:53 -07001786 -x (--extra) <key=value>
1787 Add a key/value pair to the 'extras' dict, which device-specific extension
1788 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001789
Doug Zongkereef39442009-04-02 12:14:19 -07001790 -v (--verbose)
1791 Show command lines being executed.
1792
1793 -h (--help)
1794 Display this usage message and exit.
Yifan Hong30910932019-10-25 20:36:55 -07001795
1796 --logfile <file>
1797 Put verbose logs to specified file (regardless of --verbose option.)
Doug Zongkereef39442009-04-02 12:14:19 -07001798"""
1799
1800def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001801 print(docstring.rstrip("\n"))
1802 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001803
1804
1805def ParseOptions(argv,
1806 docstring,
1807 extra_opts="", extra_long_opts=(),
1808 extra_option_handler=None):
1809 """Parse the options in argv and return any arguments that aren't
1810 flags. docstring is the calling module's docstring, to be displayed
1811 for errors and -h. extra_opts and extra_long_opts are for flags
1812 defined by the caller, which are processed by passing them to
1813 extra_option_handler."""
1814
1815 try:
1816 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001817 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001818 ["help", "verbose", "path=", "signapk_path=",
1819 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001820 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001821 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1822 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Dan Austin52903642019-12-12 15:44:00 -08001823 "extra=", "logfile=", "aftl_server=", "aftl_key_path=",
1824 "aftl_manufacturer_key_path=", "aftl_signer_helper="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001825 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001826 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001827 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001828 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001829 sys.exit(2)
1830
Doug Zongkereef39442009-04-02 12:14:19 -07001831 for o, a in opts:
1832 if o in ("-h", "--help"):
1833 Usage(docstring)
1834 sys.exit()
1835 elif o in ("-v", "--verbose"):
1836 OPTIONS.verbose = True
1837 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001838 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001839 elif o in ("--signapk_path",):
1840 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001841 elif o in ("--signapk_shared_library_path",):
1842 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001843 elif o in ("--extra_signapk_args",):
1844 OPTIONS.extra_signapk_args = shlex.split(a)
1845 elif o in ("--java_path",):
1846 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001847 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001848 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001849 elif o in ("--public_key_suffix",):
1850 OPTIONS.public_key_suffix = a
1851 elif o in ("--private_key_suffix",):
1852 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001853 elif o in ("--boot_signer_path",):
1854 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001855 elif o in ("--boot_signer_args",):
1856 OPTIONS.boot_signer_args = shlex.split(a)
1857 elif o in ("--verity_signer_path",):
1858 OPTIONS.verity_signer_path = a
1859 elif o in ("--verity_signer_args",):
1860 OPTIONS.verity_signer_args = shlex.split(a)
Dan Austin52903642019-12-12 15:44:00 -08001861 elif o in ("--aftl_server",):
1862 OPTIONS.aftl_server = a
1863 elif o in ("--aftl_key_path",):
1864 OPTIONS.aftl_key_path = a
1865 elif o in ("--aftl_manufacturer_key_path",):
1866 OPTIONS.aftl_manufacturer_key_path = a
1867 elif o in ("--aftl_signer_helper",):
1868 OPTIONS.aftl_signer_helper = a
Doug Zongker05d3dea2009-06-22 11:32:31 -07001869 elif o in ("-s", "--device_specific"):
1870 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001871 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001872 key, value = a.split("=", 1)
1873 OPTIONS.extras[key] = value
Yifan Hong30910932019-10-25 20:36:55 -07001874 elif o in ("--logfile",):
1875 OPTIONS.logfile = a
Doug Zongkereef39442009-04-02 12:14:19 -07001876 else:
1877 if extra_option_handler is None or not extra_option_handler(o, a):
1878 assert False, "unknown option \"%s\"" % (o,)
1879
Doug Zongker85448772014-09-09 14:59:20 -07001880 if OPTIONS.search_path:
1881 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1882 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001883
1884 return args
1885
1886
Tao Bao4c851b12016-09-19 13:54:38 -07001887def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001888 """Make a temp file and add it to the list of things to be deleted
1889 when Cleanup() is called. Return the filename."""
1890 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1891 os.close(fd)
1892 OPTIONS.tempfiles.append(fn)
1893 return fn
1894
1895
Tao Bao1c830bf2017-12-25 10:43:47 -08001896def MakeTempDir(prefix='tmp', suffix=''):
1897 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1898
1899 Returns:
1900 The absolute pathname of the new directory.
1901 """
1902 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1903 OPTIONS.tempfiles.append(dir_name)
1904 return dir_name
1905
1906
Doug Zongkereef39442009-04-02 12:14:19 -07001907def Cleanup():
1908 for i in OPTIONS.tempfiles:
1909 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001910 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001911 else:
1912 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001913 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001914
1915
1916class PasswordManager(object):
1917 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001918 self.editor = os.getenv("EDITOR")
1919 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001920
1921 def GetPasswords(self, items):
1922 """Get passwords corresponding to each string in 'items',
1923 returning a dict. (The dict may have keys in addition to the
1924 values in 'items'.)
1925
1926 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1927 user edit that file to add more needed passwords. If no editor is
1928 available, or $ANDROID_PW_FILE isn't define, prompts the user
1929 interactively in the ordinary way.
1930 """
1931
1932 current = self.ReadFile()
1933
1934 first = True
1935 while True:
1936 missing = []
1937 for i in items:
1938 if i not in current or not current[i]:
1939 missing.append(i)
1940 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001941 if not missing:
1942 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001943
1944 for i in missing:
1945 current[i] = ""
1946
1947 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001948 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001949 if sys.version_info[0] >= 3:
1950 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001951 answer = raw_input("try to edit again? [y]> ").strip()
1952 if answer and answer[0] not in 'yY':
1953 raise RuntimeError("key passwords unavailable")
1954 first = False
1955
1956 current = self.UpdateAndReadFile(current)
1957
Dan Albert8b72aef2015-03-23 19:13:21 -07001958 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001959 """Prompt the user to enter a value (password) for each key in
1960 'current' whose value is fales. Returns a new dict with all the
1961 values.
1962 """
1963 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001964 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001965 if v:
1966 result[k] = v
1967 else:
1968 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001969 result[k] = getpass.getpass(
1970 "Enter password for %s key> " % k).strip()
1971 if result[k]:
1972 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001973 return result
1974
1975 def UpdateAndReadFile(self, current):
1976 if not self.editor or not self.pwfile:
1977 return self.PromptResult(current)
1978
1979 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001980 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001981 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1982 f.write("# (Additional spaces are harmless.)\n\n")
1983
1984 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001985 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001986 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001987 f.write("[[[ %s ]]] %s\n" % (v, k))
1988 if not v and first_line is None:
1989 # position cursor on first line with no password.
1990 first_line = i + 4
1991 f.close()
1992
Tao Bao986ee862018-10-04 15:46:16 -07001993 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001994
1995 return self.ReadFile()
1996
1997 def ReadFile(self):
1998 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001999 if self.pwfile is None:
2000 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07002001 try:
2002 f = open(self.pwfile, "r")
2003 for line in f:
2004 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07002005 if not line or line[0] == '#':
2006 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07002007 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
2008 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07002009 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07002010 else:
2011 result[m.group(2)] = m.group(1)
2012 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07002013 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07002014 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07002015 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07002016 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07002017
2018
Dan Albert8e0178d2015-01-27 15:53:15 -08002019def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
2020 compress_type=None):
2021 import datetime
2022
2023 # http://b/18015246
2024 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
2025 # for files larger than 2GiB. We can work around this by adjusting their
2026 # limit. Note that `zipfile.writestr()` will not work for strings larger than
2027 # 2GiB. The Python interpreter sometimes rejects strings that large (though
2028 # it isn't clear to me exactly what circumstances cause this).
2029 # `zipfile.write()` must be used directly to work around this.
2030 #
2031 # This mess can be avoided if we port to python3.
2032 saved_zip64_limit = zipfile.ZIP64_LIMIT
2033 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2034
2035 if compress_type is None:
2036 compress_type = zip_file.compression
2037 if arcname is None:
2038 arcname = filename
2039
2040 saved_stat = os.stat(filename)
2041
2042 try:
2043 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
2044 # file to be zipped and reset it when we're done.
2045 os.chmod(filename, perms)
2046
2047 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07002048 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
2049 # intentional. zip stores datetimes in local time without a time zone
2050 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
2051 # in the zip archive.
2052 local_epoch = datetime.datetime.fromtimestamp(0)
2053 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08002054 os.utime(filename, (timestamp, timestamp))
2055
2056 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
2057 finally:
2058 os.chmod(filename, saved_stat.st_mode)
2059 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
2060 zipfile.ZIP64_LIMIT = saved_zip64_limit
2061
2062
Tao Bao58c1b962015-05-20 09:32:18 -07002063def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07002064 compress_type=None):
2065 """Wrap zipfile.writestr() function to work around the zip64 limit.
2066
2067 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
2068 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
2069 when calling crc32(bytes).
2070
2071 But it still works fine to write a shorter string into a large zip file.
2072 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
2073 when we know the string won't be too long.
2074 """
2075
2076 saved_zip64_limit = zipfile.ZIP64_LIMIT
2077 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2078
2079 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
2080 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07002081 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07002082 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07002083 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08002084 else:
Tao Baof3282b42015-04-01 11:21:55 -07002085 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07002086 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
2087 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
2088 # such a case (since
2089 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
2090 # which seems to make more sense. Otherwise the entry will have 0o000 as the
2091 # permission bits. We follow the logic in Python 3 to get consistent
2092 # behavior between using the two versions.
2093 if not zinfo.external_attr:
2094 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07002095
2096 # If compress_type is given, it overrides the value in zinfo.
2097 if compress_type is not None:
2098 zinfo.compress_type = compress_type
2099
Tao Bao58c1b962015-05-20 09:32:18 -07002100 # If perms is given, it has a priority.
2101 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07002102 # If perms doesn't set the file type, mark it as a regular file.
2103 if perms & 0o770000 == 0:
2104 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07002105 zinfo.external_attr = perms << 16
2106
Tao Baof3282b42015-04-01 11:21:55 -07002107 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07002108 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
2109
Dan Albert8b72aef2015-03-23 19:13:21 -07002110 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07002111 zipfile.ZIP64_LIMIT = saved_zip64_limit
2112
2113
Tao Bao89d7ab22017-12-14 17:05:33 -08002114def ZipDelete(zip_filename, entries):
2115 """Deletes entries from a ZIP file.
2116
2117 Since deleting entries from a ZIP file is not supported, it shells out to
2118 'zip -d'.
2119
2120 Args:
2121 zip_filename: The name of the ZIP file.
2122 entries: The name of the entry, or the list of names to be deleted.
2123
2124 Raises:
2125 AssertionError: In case of non-zero return from 'zip'.
2126 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07002127 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08002128 entries = [entries]
2129 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07002130 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08002131
2132
Tao Baof3282b42015-04-01 11:21:55 -07002133def ZipClose(zip_file):
2134 # http://b/18015246
2135 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
2136 # central directory.
2137 saved_zip64_limit = zipfile.ZIP64_LIMIT
2138 zipfile.ZIP64_LIMIT = (1 << 32) - 1
2139
2140 zip_file.close()
2141
2142 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07002143
2144
2145class DeviceSpecificParams(object):
2146 module = None
2147 def __init__(self, **kwargs):
2148 """Keyword arguments to the constructor become attributes of this
2149 object, which is passed to all functions in the device-specific
2150 module."""
Tao Bao38884282019-07-10 22:20:56 -07002151 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07002152 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08002153 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07002154
2155 if self.module is None:
2156 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07002157 if not path:
2158 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002159 try:
2160 if os.path.isdir(path):
2161 info = imp.find_module("releasetools", [path])
2162 else:
2163 d, f = os.path.split(path)
2164 b, x = os.path.splitext(f)
2165 if x == ".py":
2166 f = b
2167 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07002168 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07002169 self.module = imp.load_module("device_specific", *info)
2170 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07002171 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002172
2173 def _DoCall(self, function_name, *args, **kwargs):
2174 """Call the named function in the device-specific module, passing
2175 the given args and kwargs. The first argument to the call will be
2176 the DeviceSpecific object itself. If there is no module, or the
2177 module does not define the function, return the value of the
2178 'default' kwarg (which itself defaults to None)."""
2179 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08002180 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07002181 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
2182
2183 def FullOTA_Assertions(self):
2184 """Called after emitting the block of assertions at the top of a
2185 full OTA package. Implementations can add whatever additional
2186 assertions they like."""
2187 return self._DoCall("FullOTA_Assertions")
2188
Doug Zongkere5ff5902012-01-17 10:55:37 -08002189 def FullOTA_InstallBegin(self):
2190 """Called at the start of full OTA installation."""
2191 return self._DoCall("FullOTA_InstallBegin")
2192
Yifan Hong10c530d2018-12-27 17:34:18 -08002193 def FullOTA_GetBlockDifferences(self):
2194 """Called during full OTA installation and verification.
2195 Implementation should return a list of BlockDifference objects describing
2196 the update on each additional partitions.
2197 """
2198 return self._DoCall("FullOTA_GetBlockDifferences")
2199
Doug Zongker05d3dea2009-06-22 11:32:31 -07002200 def FullOTA_InstallEnd(self):
2201 """Called at the end of full OTA installation; typically this is
2202 used to install the image for the device's baseband processor."""
2203 return self._DoCall("FullOTA_InstallEnd")
2204
2205 def IncrementalOTA_Assertions(self):
2206 """Called after emitting the block of assertions at the top of an
2207 incremental OTA package. Implementations can add whatever
2208 additional assertions they like."""
2209 return self._DoCall("IncrementalOTA_Assertions")
2210
Doug Zongkere5ff5902012-01-17 10:55:37 -08002211 def IncrementalOTA_VerifyBegin(self):
2212 """Called at the start of the verification phase of incremental
2213 OTA installation; additional checks can be placed here to abort
2214 the script before any changes are made."""
2215 return self._DoCall("IncrementalOTA_VerifyBegin")
2216
Doug Zongker05d3dea2009-06-22 11:32:31 -07002217 def IncrementalOTA_VerifyEnd(self):
2218 """Called at the end of the verification phase of incremental OTA
2219 installation; additional checks can be placed here to abort the
2220 script before any changes are made."""
2221 return self._DoCall("IncrementalOTA_VerifyEnd")
2222
Doug Zongkere5ff5902012-01-17 10:55:37 -08002223 def IncrementalOTA_InstallBegin(self):
2224 """Called at the start of incremental OTA installation (after
2225 verification is complete)."""
2226 return self._DoCall("IncrementalOTA_InstallBegin")
2227
Yifan Hong10c530d2018-12-27 17:34:18 -08002228 def IncrementalOTA_GetBlockDifferences(self):
2229 """Called during incremental OTA installation and verification.
2230 Implementation should return a list of BlockDifference objects describing
2231 the update on each additional partitions.
2232 """
2233 return self._DoCall("IncrementalOTA_GetBlockDifferences")
2234
Doug Zongker05d3dea2009-06-22 11:32:31 -07002235 def IncrementalOTA_InstallEnd(self):
2236 """Called at the end of incremental OTA installation; typically
2237 this is used to install the image for the device's baseband
2238 processor."""
2239 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002240
Tao Bao9bc6bb22015-11-09 16:58:28 -08002241 def VerifyOTA_Assertions(self):
2242 return self._DoCall("VerifyOTA_Assertions")
2243
Tao Bao76def242017-11-21 09:25:31 -08002244
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002245class File(object):
Tao Bao76def242017-11-21 09:25:31 -08002246 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002247 self.name = name
2248 self.data = data
2249 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09002250 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08002251 self.sha1 = sha1(data).hexdigest()
2252
2253 @classmethod
2254 def FromLocalFile(cls, name, diskname):
2255 f = open(diskname, "rb")
2256 data = f.read()
2257 f.close()
2258 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002259
2260 def WriteToTemp(self):
2261 t = tempfile.NamedTemporaryFile()
2262 t.write(self.data)
2263 t.flush()
2264 return t
2265
Dan Willemsen2ee00d52017-03-05 19:51:56 -08002266 def WriteToDir(self, d):
2267 with open(os.path.join(d, self.name), "wb") as fp:
2268 fp.write(self.data)
2269
Geremy Condra36bd3652014-02-06 19:45:10 -08002270 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07002271 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002272
Tao Bao76def242017-11-21 09:25:31 -08002273
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002274DIFF_PROGRAM_BY_EXT = {
2275 ".gz" : "imgdiff",
2276 ".zip" : ["imgdiff", "-z"],
2277 ".jar" : ["imgdiff", "-z"],
2278 ".apk" : ["imgdiff", "-z"],
2279 ".img" : "imgdiff",
2280 }
2281
Tao Bao76def242017-11-21 09:25:31 -08002282
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002283class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07002284 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002285 self.tf = tf
2286 self.sf = sf
2287 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07002288 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002289
2290 def ComputePatch(self):
2291 """Compute the patch (as a string of data) needed to turn sf into
2292 tf. Returns the same tuple as GetPatch()."""
2293
2294 tf = self.tf
2295 sf = self.sf
2296
Doug Zongker24cd2802012-08-14 16:36:15 -07002297 if self.diff_program:
2298 diff_program = self.diff_program
2299 else:
2300 ext = os.path.splitext(tf.name)[1]
2301 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002302
2303 ttemp = tf.WriteToTemp()
2304 stemp = sf.WriteToTemp()
2305
2306 ext = os.path.splitext(tf.name)[1]
2307
2308 try:
2309 ptemp = tempfile.NamedTemporaryFile()
2310 if isinstance(diff_program, list):
2311 cmd = copy.copy(diff_program)
2312 else:
2313 cmd = [diff_program]
2314 cmd.append(stemp.name)
2315 cmd.append(ttemp.name)
2316 cmd.append(ptemp.name)
2317 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07002318 err = []
2319 def run():
2320 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07002321 if e:
2322 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07002323 th = threading.Thread(target=run)
2324 th.start()
2325 th.join(timeout=300) # 5 mins
2326 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07002327 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07002328 p.terminate()
2329 th.join(5)
2330 if th.is_alive():
2331 p.kill()
2332 th.join()
2333
Tianjie Xua2a9f992018-01-05 15:15:54 -08002334 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07002335 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07002336 self.patch = None
2337 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002338 diff = ptemp.read()
2339 finally:
2340 ptemp.close()
2341 stemp.close()
2342 ttemp.close()
2343
2344 self.patch = diff
2345 return self.tf, self.sf, self.patch
2346
2347
2348 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08002349 """Returns a tuple of (target_file, source_file, patch_data).
2350
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002351 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08002352 computing the patch failed.
2353 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002354 return self.tf, self.sf, self.patch
2355
2356
2357def ComputeDifferences(diffs):
2358 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07002359 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002360
2361 # Do the largest files first, to try and reduce the long-pole effect.
2362 by_size = [(i.tf.size, i) for i in diffs]
2363 by_size.sort(reverse=True)
2364 by_size = [i[1] for i in by_size]
2365
2366 lock = threading.Lock()
2367 diff_iter = iter(by_size) # accessed under lock
2368
2369 def worker():
2370 try:
2371 lock.acquire()
2372 for d in diff_iter:
2373 lock.release()
2374 start = time.time()
2375 d.ComputePatch()
2376 dur = time.time() - start
2377 lock.acquire()
2378
2379 tf, sf, patch = d.GetPatch()
2380 if sf.name == tf.name:
2381 name = tf.name
2382 else:
2383 name = "%s (%s)" % (tf.name, sf.name)
2384 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002385 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002386 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002387 logger.info(
2388 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
2389 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002390 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07002391 except Exception:
2392 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07002393 raise
2394
2395 # start worker threads; wait for them all to finish.
2396 threads = [threading.Thread(target=worker)
2397 for i in range(OPTIONS.worker_threads)]
2398 for th in threads:
2399 th.start()
2400 while threads:
2401 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07002402
2403
Dan Albert8b72aef2015-03-23 19:13:21 -07002404class BlockDifference(object):
2405 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07002406 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002407 self.tgt = tgt
2408 self.src = src
2409 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002410 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07002411 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002412
Tao Baodd2a5892015-03-12 12:32:37 -07002413 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08002414 version = max(
2415 int(i) for i in
2416 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08002417 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07002418 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07002419
Tianjie Xu41976c72019-07-03 13:57:01 -07002420 b = BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
2421 version=self.version,
2422 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08002423 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002424 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08002425 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07002426 self.touched_src_ranges = b.touched_src_ranges
2427 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002428
Yifan Hong10c530d2018-12-27 17:34:18 -08002429 # On devices with dynamic partitions, for new partitions,
2430 # src is None but OPTIONS.source_info_dict is not.
2431 if OPTIONS.source_info_dict is None:
2432 is_dynamic_build = OPTIONS.info_dict.get(
2433 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002434 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07002435 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08002436 is_dynamic_build = OPTIONS.source_info_dict.get(
2437 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08002438 is_dynamic_source = partition in shlex.split(
2439 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08002440
Yifan Hongbb2658d2019-01-25 12:30:58 -08002441 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08002442 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
2443
Yifan Hongbb2658d2019-01-25 12:30:58 -08002444 # For dynamic partitions builds, check partition list in both source
2445 # and target build because new partitions may be added, and existing
2446 # partitions may be removed.
2447 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
2448
Yifan Hong10c530d2018-12-27 17:34:18 -08002449 if is_dynamic:
2450 self.device = 'map_partition("%s")' % partition
2451 else:
2452 if OPTIONS.source_info_dict is None:
2453 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
2454 else:
2455 _, device_path = GetTypeAndDevice("/" + partition,
2456 OPTIONS.source_info_dict)
2457 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002458
Tao Baod8d14be2016-02-04 14:26:02 -08002459 @property
2460 def required_cache(self):
2461 return self._required_cache
2462
Tao Bao76def242017-11-21 09:25:31 -08002463 def WriteScript(self, script, output_zip, progress=None,
2464 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002465 if not self.src:
2466 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08002467 script.Print("Patching %s image unconditionally..." % (self.partition,))
2468 else:
2469 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002470
Dan Albert8b72aef2015-03-23 19:13:21 -07002471 if progress:
2472 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002473 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08002474
2475 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08002476 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08002477
Tao Bao9bc6bb22015-11-09 16:58:28 -08002478 def WriteStrictVerifyScript(self, script):
2479 """Verify all the blocks in the care_map, including clobbered blocks.
2480
2481 This differs from the WriteVerifyScript() function: a) it prints different
2482 error messages; b) it doesn't allow half-way updated images to pass the
2483 verification."""
2484
2485 partition = self.partition
2486 script.Print("Verifying %s..." % (partition,))
2487 ranges = self.tgt.care_map
2488 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002489 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002490 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
2491 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08002492 self.device, ranges_str,
2493 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08002494 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08002495 script.AppendExtra("")
2496
Tao Baod522bdc2016-04-12 15:53:16 -07002497 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00002498 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07002499
2500 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002501 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002502 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002503
2504 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002505 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002506 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002507 ranges = self.touched_src_ranges
2508 expected_sha1 = self.touched_src_sha1
2509 else:
2510 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2511 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002512
2513 # No blocks to be checked, skipping.
2514 if not ranges:
2515 return
2516
Tao Bao5ece99d2015-05-12 11:42:31 -07002517 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002518 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002519 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002520 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2521 '"%s.patch.dat")) then' % (
2522 self.device, ranges_str, expected_sha1,
2523 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002524 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002525 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002526
Tianjie Xufc3422a2015-12-15 11:53:59 -08002527 if self.version >= 4:
2528
2529 # Bug: 21124327
2530 # When generating incrementals for the system and vendor partitions in
2531 # version 4 or newer, explicitly check the first block (which contains
2532 # the superblock) of the partition to see if it's what we expect. If
2533 # this check fails, give an explicit log message about the partition
2534 # having been remounted R/W (the most likely explanation).
2535 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002536 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002537
2538 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002539 if partition == "system":
2540 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2541 else:
2542 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002543 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002544 'ifelse (block_image_recover({device}, "{ranges}") && '
2545 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002546 'package_extract_file("{partition}.transfer.list"), '
2547 '"{partition}.new.dat", "{partition}.patch.dat"), '
2548 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002549 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002550 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002551 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002552
Tao Baodd2a5892015-03-12 12:32:37 -07002553 # Abort the OTA update. Note that the incremental OTA cannot be applied
2554 # even if it may match the checksum of the target partition.
2555 # a) If version < 3, operations like move and erase will make changes
2556 # unconditionally and damage the partition.
2557 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002558 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002559 if partition == "system":
2560 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2561 else:
2562 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2563 script.AppendExtra((
2564 'abort("E%d: %s partition has unexpected contents");\n'
2565 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002566
Yifan Hong10c530d2018-12-27 17:34:18 -08002567 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002568 partition = self.partition
2569 script.Print('Verifying the updated %s image...' % (partition,))
2570 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2571 ranges = self.tgt.care_map
2572 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002573 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002574 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002575 self.device, ranges_str,
2576 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002577
2578 # Bug: 20881595
2579 # Verify that extended blocks are really zeroed out.
2580 if self.tgt.extended:
2581 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002582 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002583 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002584 self.device, ranges_str,
2585 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002586 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002587 if partition == "system":
2588 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2589 else:
2590 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002591 script.AppendExtra(
2592 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002593 ' abort("E%d: %s partition has unexpected non-zero contents after '
2594 'OTA update");\n'
2595 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002596 else:
2597 script.Print('Verified the updated %s image.' % (partition,))
2598
Tianjie Xu209db462016-05-24 17:34:52 -07002599 if partition == "system":
2600 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2601 else:
2602 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2603
Tao Bao5fcaaef2015-06-01 13:40:49 -07002604 script.AppendExtra(
2605 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002606 ' abort("E%d: %s partition has unexpected contents after OTA '
2607 'update");\n'
2608 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002609
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002610 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002611 ZipWrite(output_zip,
2612 '{}.transfer.list'.format(self.path),
2613 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002614
Tao Bao76def242017-11-21 09:25:31 -08002615 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2616 # its size. Quailty 9 almost triples the compression time but doesn't
2617 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002618 # zip | brotli(quality 6) | brotli(quality 9)
2619 # compressed_size: 942M | 869M (~8% reduced) | 854M
2620 # compression_time: 75s | 265s | 719s
2621 # decompression_time: 15s | 25s | 25s
2622
2623 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002624 brotli_cmd = ['brotli', '--quality=6',
2625 '--output={}.new.dat.br'.format(self.path),
2626 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002627 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002628 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002629
2630 new_data_name = '{}.new.dat.br'.format(self.partition)
2631 ZipWrite(output_zip,
2632 '{}.new.dat.br'.format(self.path),
2633 new_data_name,
2634 compress_type=zipfile.ZIP_STORED)
2635 else:
2636 new_data_name = '{}.new.dat'.format(self.partition)
2637 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2638
Dan Albert8e0178d2015-01-27 15:53:15 -08002639 ZipWrite(output_zip,
2640 '{}.patch.dat'.format(self.path),
2641 '{}.patch.dat'.format(self.partition),
2642 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002643
Tianjie Xu209db462016-05-24 17:34:52 -07002644 if self.partition == "system":
2645 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2646 else:
2647 code = ErrorCode.VENDOR_UPDATE_FAILURE
2648
Yifan Hong10c530d2018-12-27 17:34:18 -08002649 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002650 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002651 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002652 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002653 device=self.device, partition=self.partition,
2654 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002655 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002656
Dan Albert8b72aef2015-03-23 19:13:21 -07002657 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002658 data = source.ReadRangeSet(ranges)
2659 ctx = sha1()
2660
2661 for p in data:
2662 ctx.update(p)
2663
2664 return ctx.hexdigest()
2665
Tao Baoe9b61912015-07-09 17:37:49 -07002666 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2667 """Return the hash value for all zero blocks."""
2668 zero_block = '\x00' * 4096
2669 ctx = sha1()
2670 for _ in range(num_blocks):
2671 ctx.update(zero_block)
2672
2673 return ctx.hexdigest()
2674
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002675
Tianjie Xu41976c72019-07-03 13:57:01 -07002676# Expose these two classes to support vendor-specific scripts
2677DataImage = images.DataImage
2678EmptyImage = images.EmptyImage
2679
Tao Bao76def242017-11-21 09:25:31 -08002680
Doug Zongker96a57e72010-09-26 14:57:41 -07002681# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002682PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002683 "ext4": "EMMC",
2684 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002685 "f2fs": "EMMC",
2686 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002687}
Doug Zongker96a57e72010-09-26 14:57:41 -07002688
Tao Bao76def242017-11-21 09:25:31 -08002689
Doug Zongker96a57e72010-09-26 14:57:41 -07002690def GetTypeAndDevice(mount_point, info):
2691 fstab = info["fstab"]
2692 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002693 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2694 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002695 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002696 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002697
2698
2699def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002700 """Parses and converts a PEM-encoded certificate into DER-encoded.
2701
2702 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2703
2704 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002705 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002706 """
2707 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002708 save = False
2709 for line in data.split("\n"):
2710 if "--END CERTIFICATE--" in line:
2711 break
2712 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002713 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002714 if "--BEGIN CERTIFICATE--" in line:
2715 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002716 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002717 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002718
Tao Bao04e1f012018-02-04 12:13:35 -08002719
2720def ExtractPublicKey(cert):
2721 """Extracts the public key (PEM-encoded) from the given certificate file.
2722
2723 Args:
2724 cert: The certificate filename.
2725
2726 Returns:
2727 The public key string.
2728
2729 Raises:
2730 AssertionError: On non-zero return from 'openssl'.
2731 """
2732 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2733 # While openssl 1.1 writes the key into the given filename followed by '-out',
2734 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2735 # stdout instead.
2736 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2737 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2738 pubkey, stderrdata = proc.communicate()
2739 assert proc.returncode == 0, \
2740 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2741 return pubkey
2742
2743
Tao Bao1ac886e2019-06-26 11:58:22 -07002744def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002745 """Extracts the AVB public key from the given public or private key.
2746
2747 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002748 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002749 key: The input key file, which should be PEM-encoded public or private key.
2750
2751 Returns:
2752 The path to the extracted AVB public key file.
2753 """
2754 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2755 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002756 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002757 return output
2758
2759
Doug Zongker412c02f2014-02-13 10:58:24 -08002760def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2761 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002762 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002763
Tao Bao6d5d6232018-03-09 17:04:42 -08002764 Most of the space in the boot and recovery images is just the kernel, which is
2765 identical for the two, so the resulting patch should be efficient. Add it to
2766 the output zip, along with a shell script that is run from init.rc on first
2767 boot to actually do the patching and install the new recovery image.
2768
2769 Args:
2770 input_dir: The top-level input directory of the target-files.zip.
2771 output_sink: The callback function that writes the result.
2772 recovery_img: File object for the recovery image.
2773 boot_img: File objects for the boot image.
2774 info_dict: A dict returned by common.LoadInfoDict() on the input
2775 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002776 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002777 if info_dict is None:
2778 info_dict = OPTIONS.info_dict
2779
Tao Bao6d5d6232018-03-09 17:04:42 -08002780 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002781 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
2782
2783 if board_uses_vendorimage:
2784 # In this case, the output sink is rooted at VENDOR
2785 recovery_img_path = "etc/recovery.img"
2786 recovery_resource_dat_path = "VENDOR/etc/recovery-resource.dat"
2787 sh_dir = "bin"
2788 else:
2789 # In this case the output sink is rooted at SYSTEM
2790 recovery_img_path = "vendor/etc/recovery.img"
2791 recovery_resource_dat_path = "SYSTEM/vendor/etc/recovery-resource.dat"
2792 sh_dir = "vendor/bin"
Doug Zongkerc9253822014-02-04 12:17:58 -08002793
Tao Baof2cffbd2015-07-22 12:33:18 -07002794 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002795 output_sink(recovery_img_path, recovery_img.data)
Tao Baof2cffbd2015-07-22 12:33:18 -07002796
2797 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002798 system_root_image = info_dict.get("system_root_image") == "true"
Bill Peckhame868aec2019-09-17 17:06:47 -07002799 path = os.path.join(input_dir, recovery_resource_dat_path)
Tao Bao6d5d6232018-03-09 17:04:42 -08002800 # With system-root-image, boot and recovery images will have mismatching
2801 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2802 # to handle such a case.
2803 if system_root_image:
2804 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002805 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002806 assert not os.path.exists(path)
2807 else:
2808 diff_program = ["imgdiff"]
2809 if os.path.exists(path):
2810 diff_program.append("-b")
2811 diff_program.append(path)
Bill Peckhame868aec2019-09-17 17:06:47 -07002812 bonus_args = "--bonus /vendor/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002813 else:
2814 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002815
2816 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2817 _, _, patch = d.ComputePatch()
2818 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002819
Dan Albertebb19aa2015-03-27 19:11:53 -07002820 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002821 # The following GetTypeAndDevice()s need to use the path in the target
2822 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002823 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2824 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2825 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002826 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002827
Tao Baof2cffbd2015-07-22 12:33:18 -07002828 if full_recovery_image:
Bill Peckhame868aec2019-09-17 17:06:47 -07002829
2830 # Note that we use /vendor to refer to the recovery resources. This will
2831 # work for a separate vendor partition mounted at /vendor or a
2832 # /system/vendor subdirectory on the system partition, for which init will
2833 # create a symlink from /vendor to /system/vendor.
2834
2835 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002836if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2837 applypatch \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002838 --flash /vendor/etc/recovery.img \\
Tao Bao4948aed2018-07-13 16:11:16 -07002839 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2840 log -t recovery "Installing new recovery image: succeeded" || \\
2841 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002842else
2843 log -t recovery "Recovery image already installed"
2844fi
2845""" % {'type': recovery_type,
2846 'device': recovery_device,
2847 'sha1': recovery_img.sha1,
2848 'size': recovery_img.size}
2849 else:
Bill Peckhame868aec2019-09-17 17:06:47 -07002850 sh = """#!/vendor/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002851if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2852 applypatch %(bonus_args)s \\
Bill Peckhame868aec2019-09-17 17:06:47 -07002853 --patch /vendor/recovery-from-boot.p \\
Tao Bao4948aed2018-07-13 16:11:16 -07002854 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2855 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2856 log -t recovery "Installing new recovery image: succeeded" || \\
2857 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002858else
2859 log -t recovery "Recovery image already installed"
2860fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002861""" % {'boot_size': boot_img.size,
2862 'boot_sha1': boot_img.sha1,
2863 'recovery_size': recovery_img.size,
2864 'recovery_sha1': recovery_img.sha1,
2865 'boot_type': boot_type,
2866 'boot_device': boot_device,
2867 'recovery_type': recovery_type,
2868 'recovery_device': recovery_device,
2869 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002870
Bill Peckhame868aec2019-09-17 17:06:47 -07002871 # The install script location moved from /system/etc to /system/bin in the L
2872 # release. In the R release it is in VENDOR/bin or SYSTEM/vendor/bin.
2873 sh_location = os.path.join(sh_dir, "install-recovery.sh")
Tao Bao9f0c8df2015-07-07 18:31:47 -07002874
Tao Bao32fcdab2018-10-12 10:30:39 -07002875 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002876
Tao Baoda30cfa2017-12-01 16:19:46 -08002877 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002878
2879
2880class DynamicPartitionUpdate(object):
2881 def __init__(self, src_group=None, tgt_group=None, progress=None,
2882 block_difference=None):
2883 self.src_group = src_group
2884 self.tgt_group = tgt_group
2885 self.progress = progress
2886 self.block_difference = block_difference
2887
2888 @property
2889 def src_size(self):
2890 if not self.block_difference:
2891 return 0
2892 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2893
2894 @property
2895 def tgt_size(self):
2896 if not self.block_difference:
2897 return 0
2898 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2899
2900 @staticmethod
2901 def _GetSparseImageSize(img):
2902 if not img:
2903 return 0
2904 return img.blocksize * img.total_blocks
2905
2906
2907class DynamicGroupUpdate(object):
2908 def __init__(self, src_size=None, tgt_size=None):
2909 # None: group does not exist. 0: no size limits.
2910 self.src_size = src_size
2911 self.tgt_size = tgt_size
2912
2913
2914class DynamicPartitionsDifference(object):
2915 def __init__(self, info_dict, block_diffs, progress_dict=None,
2916 source_info_dict=None):
2917 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002918 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002919
2920 self._remove_all_before_apply = False
2921 if source_info_dict is None:
2922 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002923 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002924
Tao Baof1113e92019-06-18 12:10:14 -07002925 block_diff_dict = collections.OrderedDict(
2926 [(e.partition, e) for e in block_diffs])
2927
Yifan Hong10c530d2018-12-27 17:34:18 -08002928 assert len(block_diff_dict) == len(block_diffs), \
2929 "Duplicated BlockDifference object for {}".format(
2930 [partition for partition, count in
2931 collections.Counter(e.partition for e in block_diffs).items()
2932 if count > 1])
2933
Yifan Hong79997e52019-01-23 16:56:19 -08002934 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002935
2936 for p, block_diff in block_diff_dict.items():
2937 self._partition_updates[p] = DynamicPartitionUpdate()
2938 self._partition_updates[p].block_difference = block_diff
2939
2940 for p, progress in progress_dict.items():
2941 if p in self._partition_updates:
2942 self._partition_updates[p].progress = progress
2943
2944 tgt_groups = shlex.split(info_dict.get(
2945 "super_partition_groups", "").strip())
2946 src_groups = shlex.split(source_info_dict.get(
2947 "super_partition_groups", "").strip())
2948
2949 for g in tgt_groups:
2950 for p in shlex.split(info_dict.get(
2951 "super_%s_partition_list" % g, "").strip()):
2952 assert p in self._partition_updates, \
2953 "{} is in target super_{}_partition_list but no BlockDifference " \
2954 "object is provided.".format(p, g)
2955 self._partition_updates[p].tgt_group = g
2956
2957 for g in src_groups:
2958 for p in shlex.split(source_info_dict.get(
2959 "super_%s_partition_list" % g, "").strip()):
2960 assert p in self._partition_updates, \
2961 "{} is in source super_{}_partition_list but no BlockDifference " \
2962 "object is provided.".format(p, g)
2963 self._partition_updates[p].src_group = g
2964
Yifan Hong45433e42019-01-18 13:55:25 -08002965 target_dynamic_partitions = set(shlex.split(info_dict.get(
2966 "dynamic_partition_list", "").strip()))
2967 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2968 if u.tgt_size)
2969 assert block_diffs_with_target == target_dynamic_partitions, \
2970 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2971 list(target_dynamic_partitions), list(block_diffs_with_target))
2972
2973 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2974 "dynamic_partition_list", "").strip()))
2975 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2976 if u.src_size)
2977 assert block_diffs_with_source == source_dynamic_partitions, \
2978 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2979 list(source_dynamic_partitions), list(block_diffs_with_source))
2980
Yifan Hong10c530d2018-12-27 17:34:18 -08002981 if self._partition_updates:
2982 logger.info("Updating dynamic partitions %s",
2983 self._partition_updates.keys())
2984
Yifan Hong79997e52019-01-23 16:56:19 -08002985 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002986
2987 for g in tgt_groups:
2988 self._group_updates[g] = DynamicGroupUpdate()
2989 self._group_updates[g].tgt_size = int(info_dict.get(
2990 "super_%s_group_size" % g, "0").strip())
2991
2992 for g in src_groups:
2993 if g not in self._group_updates:
2994 self._group_updates[g] = DynamicGroupUpdate()
2995 self._group_updates[g].src_size = int(source_info_dict.get(
2996 "super_%s_group_size" % g, "0").strip())
2997
2998 self._Compute()
2999
3000 def WriteScript(self, script, output_zip, write_verify_script=False):
3001 script.Comment('--- Start patching dynamic partitions ---')
3002 for p, u in self._partition_updates.items():
3003 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3004 script.Comment('Patch partition %s' % p)
3005 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3006 write_verify_script=False)
3007
3008 op_list_path = MakeTempFile()
3009 with open(op_list_path, 'w') as f:
3010 for line in self._op_list:
3011 f.write('{}\n'.format(line))
3012
3013 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
3014
3015 script.Comment('Update dynamic partition metadata')
3016 script.AppendExtra('assert(update_dynamic_partitions('
3017 'package_extract_file("dynamic_partitions_op_list")));')
3018
3019 if write_verify_script:
3020 for p, u in self._partition_updates.items():
3021 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3022 u.block_difference.WritePostInstallVerifyScript(script)
3023 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3024
3025 for p, u in self._partition_updates.items():
3026 if u.tgt_size and u.src_size <= u.tgt_size:
3027 script.Comment('Patch partition %s' % p)
3028 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
3029 write_verify_script=write_verify_script)
3030 if write_verify_script:
3031 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
3032
3033 script.Comment('--- End patching dynamic partitions ---')
3034
3035 def _Compute(self):
3036 self._op_list = list()
3037
3038 def append(line):
3039 self._op_list.append(line)
3040
3041 def comment(line):
3042 self._op_list.append("# %s" % line)
3043
3044 if self._remove_all_before_apply:
3045 comment('Remove all existing dynamic partitions and groups before '
3046 'applying full OTA')
3047 append('remove_all_groups')
3048
3049 for p, u in self._partition_updates.items():
3050 if u.src_group and not u.tgt_group:
3051 append('remove %s' % p)
3052
3053 for p, u in self._partition_updates.items():
3054 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3055 comment('Move partition %s from %s to default' % (p, u.src_group))
3056 append('move %s default' % p)
3057
3058 for p, u in self._partition_updates.items():
3059 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
3060 comment('Shrink partition %s from %d to %d' %
3061 (p, u.src_size, u.tgt_size))
3062 append('resize %s %s' % (p, u.tgt_size))
3063
3064 for g, u in self._group_updates.items():
3065 if u.src_size is not None and u.tgt_size is None:
3066 append('remove_group %s' % g)
3067 if (u.src_size is not None and u.tgt_size is not None and
3068 u.src_size > u.tgt_size):
3069 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3070 append('resize_group %s %d' % (g, u.tgt_size))
3071
3072 for g, u in self._group_updates.items():
3073 if u.src_size is None and u.tgt_size is not None:
3074 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
3075 append('add_group %s %d' % (g, u.tgt_size))
3076 if (u.src_size is not None and u.tgt_size is not None and
3077 u.src_size < u.tgt_size):
3078 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
3079 append('resize_group %s %d' % (g, u.tgt_size))
3080
3081 for p, u in self._partition_updates.items():
3082 if u.tgt_group and not u.src_group:
3083 comment('Add partition %s to group %s' % (p, u.tgt_group))
3084 append('add %s %s' % (p, u.tgt_group))
3085
3086 for p, u in self._partition_updates.items():
3087 if u.tgt_size and u.src_size < u.tgt_size:
3088 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
3089 append('resize %s %d' % (p, u.tgt_size))
3090
3091 for p, u in self._partition_updates.items():
3092 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
3093 comment('Move partition %s from default to %s' %
3094 (p, u.tgt_group))
3095 append('move %s %s' % (p, u.tgt_group))