blob: 0030afa4b17537f2a8efa0071bee3c8c102c1699 [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
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070042import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070044
Tao Bao32fcdab2018-10-12 10:30:39 -070045logger = logging.getLogger(__name__)
46
Tao Bao986ee862018-10-04 15:46:16 -070047
Dan Albert8b72aef2015-03-23 19:13:21 -070048class Options(object):
49 def __init__(self):
Pavel Salomatov32676552019-03-06 20:00:45 +030050 base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
51 if base_out_path is None:
52 base_search_path = "out"
53 else:
Tao Bao2cc0ca12019-03-15 10:44:43 -070054 base_search_path = os.path.join(base_out_path,
55 os.path.basename(os.getcwd()))
Pavel Salomatov32676552019-03-06 20:00:45 +030056
Tao Baoa3705452019-06-24 15:33:41 -070057 # Python >= 3.3 returns 'linux', whereas Python 2.7 gives 'linux2'.
Dan Albert8b72aef2015-03-23 19:13:21 -070058 platform_search_path = {
Tao Baoa3705452019-06-24 15:33:41 -070059 "linux": os.path.join(base_search_path, "host/linux-x86"),
Pavel Salomatov32676552019-03-06 20:00:45 +030060 "linux2": os.path.join(base_search_path, "host/linux-x86"),
61 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070062 }
Doug Zongker85448772014-09-09 14:59:20 -070063
Tao Bao76def242017-11-21 09:25:31 -080064 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070065 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080066 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.extra_signapk_args = []
68 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080069 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070070 self.public_key_suffix = ".x509.pem"
71 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070072 # use otatools built boot_signer by default
73 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070074 self.boot_signer_args = []
75 self.verity_signer_path = None
76 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070077 self.verbose = False
78 self.tempfiles = []
79 self.device_specific = None
80 self.extras = {}
81 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070082 self.source_info_dict = None
83 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070084 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070085 # Stash size cannot exceed cache_size * threshold.
86 self.cache_size = None
87 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070088
89
90OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070091
Tao Bao71197512018-10-11 14:08:45 -070092# The block size that's used across the releasetools scripts.
93BLOCK_SIZE = 4096
94
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080095# Values for "certificate" in apkcerts that mean special things.
96SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
97
Tao Bao5cc0abb2019-03-21 10:18:05 -070098# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
99# that system_other is not in the list because we don't want to include its
100# descriptor into vbmeta.img.
Justin Yun6151e3f2019-06-25 15:58:13 +0900101AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'recovery', 'system',
102 'system_ext', 'vendor')
Tao Bao9dd909e2017-11-14 11:27:32 -0800103
Tao Bao08c190f2019-06-03 23:07:58 -0700104# Chained VBMeta partitions.
105AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
106
Tianjie Xu861f4132018-09-12 11:49:33 -0700107# Partitions that should have their care_map added to META/care_map.pb
Justin Yun6151e3f2019-06-25 15:58:13 +0900108PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'system_ext', 'odm')
Tianjie Xu861f4132018-09-12 11:49:33 -0700109
110
Tianjie Xu209db462016-05-24 17:34:52 -0700111class ErrorCode(object):
112 """Define error_codes for failures that happen during the actual
113 update package installation.
114
115 Error codes 0-999 are reserved for failures before the package
116 installation (i.e. low battery, package verification failure).
117 Detailed code in 'bootable/recovery/error_code.h' """
118
119 SYSTEM_VERIFICATION_FAILURE = 1000
120 SYSTEM_UPDATE_FAILURE = 1001
121 SYSTEM_UNEXPECTED_CONTENTS = 1002
122 SYSTEM_NONZERO_CONTENTS = 1003
123 SYSTEM_RECOVER_FAILURE = 1004
124 VENDOR_VERIFICATION_FAILURE = 2000
125 VENDOR_UPDATE_FAILURE = 2001
126 VENDOR_UNEXPECTED_CONTENTS = 2002
127 VENDOR_NONZERO_CONTENTS = 2003
128 VENDOR_RECOVER_FAILURE = 2004
129 OEM_PROP_MISMATCH = 3000
130 FINGERPRINT_MISMATCH = 3001
131 THUMBPRINT_MISMATCH = 3002
132 OLDER_BUILD = 3003
133 DEVICE_MISMATCH = 3004
134 BAD_PATCH_FILE = 3005
135 INSUFFICIENT_CACHE_SPACE = 3006
136 TUNE_PARTITION_FAILURE = 3007
137 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800138
Tao Bao80921982018-03-21 21:02:19 -0700139
Dan Albert8b72aef2015-03-23 19:13:21 -0700140class ExternalError(RuntimeError):
141 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700142
143
Tao Bao32fcdab2018-10-12 10:30:39 -0700144def InitLogging():
145 DEFAULT_LOGGING_CONFIG = {
146 'version': 1,
147 'disable_existing_loggers': False,
148 'formatters': {
149 'standard': {
150 'format':
151 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
152 'datefmt': '%Y-%m-%d %H:%M:%S',
153 },
154 },
155 'handlers': {
156 'default': {
157 'class': 'logging.StreamHandler',
158 'formatter': 'standard',
159 },
160 },
161 'loggers': {
162 '': {
163 'handlers': ['default'],
164 'level': 'WARNING',
165 'propagate': True,
166 }
167 }
168 }
169 env_config = os.getenv('LOGGING_CONFIG')
170 if env_config:
171 with open(env_config) as f:
172 config = json.load(f)
173 else:
174 config = DEFAULT_LOGGING_CONFIG
175
176 # Increase the logging level for verbose mode.
177 if OPTIONS.verbose:
178 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
179 config['loggers']['']['level'] = 'INFO'
180
181 logging.config.dictConfig(config)
182
183
Tao Bao39451582017-05-04 11:10:47 -0700184def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700185 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700186
Tao Bao73dd4f42018-10-04 16:25:33 -0700187 Args:
188 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700189 verbose: Whether the commands should be shown. Default to the global
190 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700191 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
192 stdin, etc. stdout and stderr will default to subprocess.PIPE and
193 subprocess.STDOUT respectively unless caller specifies any of them.
Tao Baoda30cfa2017-12-01 16:19:46 -0800194 universal_newlines will default to True, as most of the users in
195 releasetools expect string output.
Tao Bao73dd4f42018-10-04 16:25:33 -0700196
197 Returns:
198 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700199 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700200 if 'stdout' not in kwargs and 'stderr' not in kwargs:
201 kwargs['stdout'] = subprocess.PIPE
202 kwargs['stderr'] = subprocess.STDOUT
Tao Baoda30cfa2017-12-01 16:19:46 -0800203 if 'universal_newlines' not in kwargs:
204 kwargs['universal_newlines'] = True
Tao Bao32fcdab2018-10-12 10:30:39 -0700205 # Don't log any if caller explicitly says so.
206 if verbose != False:
207 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700208 return subprocess.Popen(args, **kwargs)
209
210
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800211def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800212 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800213
214 Args:
215 args: The command represented as a list of strings.
216 verbose: Whether the commands should be shown. Default to the global
217 verbosity if unspecified.
218 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
219 stdin, etc. stdout and stderr will default to subprocess.PIPE and
220 subprocess.STDOUT respectively unless caller specifies any of them.
221
Bill Peckham889b0c62019-02-21 18:53:37 -0800222 Raises:
223 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800224 """
225 proc = Run(args, verbose=verbose, **kwargs)
226 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800227
228 if proc.returncode != 0:
229 raise ExternalError(
230 "Failed to run command '{}' (exit code {})".format(
231 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800232
233
Tao Bao986ee862018-10-04 15:46:16 -0700234def RunAndCheckOutput(args, verbose=None, **kwargs):
235 """Runs the given command and returns the output.
236
237 Args:
238 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700239 verbose: Whether the commands should be shown. Default to the global
240 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700241 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
242 stdin, etc. stdout and stderr will default to subprocess.PIPE and
243 subprocess.STDOUT respectively unless caller specifies any of them.
244
245 Returns:
246 The output string.
247
248 Raises:
249 ExternalError: On non-zero exit from the command.
250 """
Tao Bao986ee862018-10-04 15:46:16 -0700251 proc = Run(args, verbose=verbose, **kwargs)
252 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700253 # Don't log any if caller explicitly says so.
254 if verbose != False:
255 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700256 if proc.returncode != 0:
257 raise ExternalError(
258 "Failed to run command '{}' (exit code {}):\n{}".format(
259 args, proc.returncode, output))
260 return output
261
262
Tao Baoc765cca2018-01-31 17:32:40 -0800263def RoundUpTo4K(value):
264 rounded_up = value + 4095
265 return rounded_up - (rounded_up % 4096)
266
267
Ying Wang7e6d4e42010-12-13 16:25:36 -0800268def CloseInheritedPipes():
269 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
270 before doing other work."""
271 if platform.system() != "Darwin":
272 return
273 for d in range(3, 1025):
274 try:
275 stat = os.fstat(d)
276 if stat is not None:
277 pipebit = stat[0] & 0x1000
278 if pipebit != 0:
279 os.close(d)
280 except OSError:
281 pass
282
283
Tao Bao410ad8b2018-08-24 12:08:38 -0700284def LoadInfoDict(input_file, repacking=False):
285 """Loads the key/value pairs from the given input target_files.
286
287 It reads `META/misc_info.txt` file in the target_files input, does sanity
288 checks and returns the parsed key/value pairs for to the given build. It's
289 usually called early when working on input target_files files, e.g. when
290 generating OTAs, or signing builds. Note that the function may be called
291 against an old target_files file (i.e. from past dessert releases). So the
292 property parsing needs to be backward compatible.
293
294 In a `META/misc_info.txt`, a few properties are stored as links to the files
295 in the PRODUCT_OUT directory. It works fine with the build system. However,
296 they are no longer available when (re)generating images from target_files zip.
297 When `repacking` is True, redirect these properties to the actual files in the
298 unzipped directory.
299
300 Args:
301 input_file: The input target_files file, which could be an open
302 zipfile.ZipFile instance, or a str for the dir that contains the files
303 unzipped from a target_files file.
304 repacking: Whether it's trying repack an target_files file after loading the
305 info dict (default: False). If so, it will rewrite a few loaded
306 properties (e.g. selinux_fc, root_dir) to point to the actual files in
307 target_files file. When doing repacking, `input_file` must be a dir.
308
309 Returns:
310 A dict that contains the parsed key/value pairs.
311
312 Raises:
313 AssertionError: On invalid input arguments.
314 ValueError: On malformed input values.
315 """
316 if repacking:
317 assert isinstance(input_file, str), \
318 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700319
Doug Zongkerc9253822014-02-04 12:17:58 -0800320 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700321 if isinstance(input_file, zipfile.ZipFile):
Tao Baoda30cfa2017-12-01 16:19:46 -0800322 return input_file.read(fn).decode()
Doug Zongkerc9253822014-02-04 12:17:58 -0800323 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700324 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800325 try:
326 with open(path) as f:
327 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700328 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800329 if e.errno == errno.ENOENT:
330 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800331
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700332 try:
Michael Runge6e836112014-04-15 17:40:21 -0700333 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700334 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700335 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700336
Tao Bao410ad8b2018-08-24 12:08:38 -0700337 if "recovery_api_version" not in d:
338 raise ValueError("Failed to find 'recovery_api_version'")
339 if "fstab_version" not in d:
340 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800341
Tao Bao410ad8b2018-08-24 12:08:38 -0700342 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700343 # "selinux_fc" properties should point to the file_contexts files
344 # (file_contexts.bin) under META/.
345 for key in d:
346 if key.endswith("selinux_fc"):
347 fc_basename = os.path.basename(d[key])
348 fc_config = os.path.join(input_file, "META", fc_basename)
349 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700350
Daniel Norman72c626f2019-05-13 15:58:14 -0700351 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700352
Tom Cherryd14b8952018-08-09 14:26:00 -0700353 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700354 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700355 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700356 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700357
Tao Baof54216f2016-03-29 15:12:37 -0700358 # Redirect {system,vendor}_base_fs_file.
359 if "system_base_fs_file" in d:
360 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700361 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700362 if os.path.exists(system_base_fs_file):
363 d["system_base_fs_file"] = system_base_fs_file
364 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700365 logger.warning(
366 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700367 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700368
369 if "vendor_base_fs_file" in d:
370 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700371 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700372 if os.path.exists(vendor_base_fs_file):
373 d["vendor_base_fs_file"] = vendor_base_fs_file
374 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700375 logger.warning(
376 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700377 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700378
Doug Zongker37974732010-09-16 17:44:38 -0700379 def makeint(key):
380 if key in d:
381 d[key] = int(d[key], 0)
382
383 makeint("recovery_api_version")
384 makeint("blocksize")
385 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700386 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700387 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700388 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700389 makeint("recovery_size")
390 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800391 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700392
Tao Baoa57ab9f2018-08-24 12:08:38 -0700393 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
394 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
395 # cases, since it may load the info_dict from an old build (e.g. when
396 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800397 system_root_image = d.get("system_root_image") == "true"
398 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700399 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700400 if isinstance(input_file, zipfile.ZipFile):
401 if recovery_fstab_path not in input_file.namelist():
402 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
403 else:
404 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
405 if not os.path.exists(path):
406 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800407 d["fstab"] = LoadRecoveryFSTab(
408 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700409
Tao Bao76def242017-11-21 09:25:31 -0800410 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700411 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700412 if isinstance(input_file, zipfile.ZipFile):
413 if recovery_fstab_path not in input_file.namelist():
414 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
415 else:
416 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
417 if not os.path.exists(path):
418 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800419 d["fstab"] = LoadRecoveryFSTab(
420 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700421
Tianjie Xucfa86222016-03-07 16:31:19 -0800422 else:
423 d["fstab"] = None
424
Tianjie Xu861f4132018-09-12 11:49:33 -0700425 # Tries to load the build props for all partitions with care_map, including
426 # system and vendor.
427 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800428 partition_prop = "{}.build.prop".format(partition)
429 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700430 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800431 # Some partition might use /<partition>/etc/build.prop as the new path.
432 # TODO: try new path first when majority of them switch to the new path.
433 if not d[partition_prop]:
434 d[partition_prop] = LoadBuildProp(
435 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700436 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800437
438 # Set up the salt (based on fingerprint or thumbprint) that will be used when
439 # adding AVB footer.
440 if d.get("avb_enable") == "true":
441 fp = None
442 if "build.prop" in d:
443 build_prop = d["build.prop"]
444 if "ro.build.fingerprint" in build_prop:
445 fp = build_prop["ro.build.fingerprint"]
446 elif "ro.build.thumbprint" in build_prop:
447 fp = build_prop["ro.build.thumbprint"]
448 if fp:
449 d["avb_salt"] = sha256(fp).hexdigest()
450
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700451 return d
452
Tao Baod1de6f32017-03-01 16:38:48 -0800453
Tao Baobcd1d162017-08-26 13:10:26 -0700454def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700455 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700456 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700457 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700458 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700459 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700460 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700461
Tao Baod1de6f32017-03-01 16:38:48 -0800462
Kiyoung Kimebe7c9c2019-06-25 17:09:55 +0900463def LoadDictionaryFromFile(file_path):
464 with open(file_path) as f:
465 lines = list(f.read().splitlines())
466
467 return LoadDictionaryFromLines(lines)
468
469
Michael Runge6e836112014-04-15 17:40:21 -0700470def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700471 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700472 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700473 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700474 if not line or line.startswith("#"):
475 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700476 if "=" in line:
477 name, value = line.split("=", 1)
478 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700479 return d
480
Tao Baod1de6f32017-03-01 16:38:48 -0800481
Tianjie Xucfa86222016-03-07 16:31:19 -0800482def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
483 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700484 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800485 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700486 self.mount_point = mount_point
487 self.fs_type = fs_type
488 self.device = device
489 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700490 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700491
492 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800493 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700494 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700495 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700496 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700497
Tao Baod1de6f32017-03-01 16:38:48 -0800498 assert fstab_version == 2
499
500 d = {}
501 for line in data.split("\n"):
502 line = line.strip()
503 if not line or line.startswith("#"):
504 continue
505
506 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
507 pieces = line.split()
508 if len(pieces) != 5:
509 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
510
511 # Ignore entries that are managed by vold.
512 options = pieces[4]
513 if "voldmanaged=" in options:
514 continue
515
516 # It's a good line, parse it.
517 length = 0
518 options = options.split(",")
519 for i in options:
520 if i.startswith("length="):
521 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800522 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800523 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700524 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800525
Tao Baod1de6f32017-03-01 16:38:48 -0800526 mount_flags = pieces[3]
527 # Honor the SELinux context if present.
528 context = None
529 for i in mount_flags.split(","):
530 if i.startswith("context="):
531 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800532
Tao Baod1de6f32017-03-01 16:38:48 -0800533 mount_point = pieces[1]
534 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
535 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800536
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700537 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700538 # system. Other areas assume system is always at "/system" so point /system
539 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700540 if system_root_image:
Tao Baoda30cfa2017-12-01 16:19:46 -0800541 assert '/system' not in d and '/' in d
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700542 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700543 return d
544
545
Doug Zongker37974732010-09-16 17:44:38 -0700546def DumpInfoDict(d):
547 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700548 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700549
Dan Albert8b72aef2015-03-23 19:13:21 -0700550
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800551def AppendAVBSigningArgs(cmd, partition):
552 """Append signing arguments for avbtool."""
553 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
554 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
555 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
556 if key_path and algorithm:
557 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700558 avb_salt = OPTIONS.info_dict.get("avb_salt")
559 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700560 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700561 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800562
563
Tao Bao02a08592018-07-22 12:40:45 -0700564def GetAvbChainedPartitionArg(partition, info_dict, key=None):
565 """Constructs and returns the arg to build or verify a chained partition.
566
567 Args:
568 partition: The partition name.
569 info_dict: The info dict to look up the key info and rollback index
570 location.
571 key: The key to be used for building or verifying the partition. Defaults to
572 the key listed in info_dict.
573
574 Returns:
575 A string of form "partition:rollback_index_location:key" that can be used to
576 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700577 """
578 if key is None:
579 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao1ac886e2019-06-26 11:58:22 -0700580 pubkey_path = ExtractAvbPublicKey(info_dict["avb_avbtool"], key)
Tao Bao02a08592018-07-22 12:40:45 -0700581 rollback_index_location = info_dict[
582 "avb_" + partition + "_rollback_index_location"]
583 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
584
585
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700586def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800587 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700588 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700589
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700590 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800591 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
592 we are building a two-step special image (i.e. building a recovery image to
593 be loaded into /boot in two-step OTAs).
594
595 Return the image data, or None if sourcedir does not appear to contains files
596 for building the requested image.
597 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700598
599 def make_ramdisk():
600 ramdisk_img = tempfile.NamedTemporaryFile()
601
602 if os.access(fs_config_file, os.F_OK):
603 cmd = ["mkbootfs", "-f", fs_config_file,
604 os.path.join(sourcedir, "RAMDISK")]
605 else:
606 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
607 p1 = Run(cmd, stdout=subprocess.PIPE)
608 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
609
610 p2.wait()
611 p1.wait()
612 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
613 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
614
615 return ramdisk_img
616
617 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
618 return None
619
620 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700621 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700622
Doug Zongkerd5131602012-08-02 14:46:42 -0700623 if info_dict is None:
624 info_dict = OPTIONS.info_dict
625
Doug Zongkereef39442009-04-02 12:14:19 -0700626 img = tempfile.NamedTemporaryFile()
627
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700628 if has_ramdisk:
629 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700630
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800631 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
632 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
633
634 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700635
Benoit Fradina45a8682014-07-14 21:00:43 +0200636 fn = os.path.join(sourcedir, "second")
637 if os.access(fn, os.F_OK):
638 cmd.append("--second")
639 cmd.append(fn)
640
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800641 fn = os.path.join(sourcedir, "dtb")
642 if os.access(fn, os.F_OK):
643 cmd.append("--dtb")
644 cmd.append(fn)
645
Doug Zongker171f1cd2009-06-15 22:36:37 -0700646 fn = os.path.join(sourcedir, "cmdline")
647 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700648 cmd.append("--cmdline")
649 cmd.append(open(fn).read().rstrip("\n"))
650
651 fn = os.path.join(sourcedir, "base")
652 if os.access(fn, os.F_OK):
653 cmd.append("--base")
654 cmd.append(open(fn).read().rstrip("\n"))
655
Ying Wang4de6b5b2010-08-25 14:29:34 -0700656 fn = os.path.join(sourcedir, "pagesize")
657 if os.access(fn, os.F_OK):
658 cmd.append("--pagesize")
659 cmd.append(open(fn).read().rstrip("\n"))
660
Tao Bao76def242017-11-21 09:25:31 -0800661 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700662 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700663 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700664
Tao Bao76def242017-11-21 09:25:31 -0800665 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000666 if args and args.strip():
667 cmd.extend(shlex.split(args))
668
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700669 if has_ramdisk:
670 cmd.extend(["--ramdisk", ramdisk_img.name])
671
Tao Baod95e9fd2015-03-29 23:07:41 -0700672 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800673 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700674 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700675 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700676 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700677 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700678
Tao Baobf70c3182017-07-11 17:27:55 -0700679 # "boot" or "recovery", without extension.
680 partition_name = os.path.basename(sourcedir).lower()
681
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800682 if partition_name == "recovery":
683 if info_dict.get("include_recovery_dtbo") == "true":
684 fn = os.path.join(sourcedir, "recovery_dtbo")
685 cmd.extend(["--recovery_dtbo", fn])
686 if info_dict.get("include_recovery_acpio") == "true":
687 fn = os.path.join(sourcedir, "recovery_acpio")
688 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700689
Tao Bao986ee862018-10-04 15:46:16 -0700690 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700691
Tao Bao76def242017-11-21 09:25:31 -0800692 if (info_dict.get("boot_signer") == "true" and
693 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800694 # Hard-code the path as "/boot" for two-step special recovery image (which
695 # will be loaded into /boot during the two-step OTA).
696 if two_step_image:
697 path = "/boot"
698 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700699 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700700 cmd = [OPTIONS.boot_signer_path]
701 cmd.extend(OPTIONS.boot_signer_args)
702 cmd.extend([path, img.name,
703 info_dict["verity_key"] + ".pk8",
704 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700705 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700706
Tao Baod95e9fd2015-03-29 23:07:41 -0700707 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800708 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700709 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700710 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800711 # We have switched from the prebuilt futility binary to using the tool
712 # (futility-host) built from the source. Override the setting in the old
713 # TF.zip.
714 futility = info_dict["futility"]
715 if futility.startswith("prebuilts/"):
716 futility = "futility-host"
717 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700718 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700719 info_dict["vboot_key"] + ".vbprivk",
720 info_dict["vboot_subkey"] + ".vbprivk",
721 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700722 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700723 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700724
Tao Baof3282b42015-04-01 11:21:55 -0700725 # Clean up the temp files.
726 img_unsigned.close()
727 img_keyblock.close()
728
David Zeuthen8fecb282017-12-01 16:24:01 -0500729 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800730 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700731 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500732 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400733 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700734 "--partition_size", str(part_size), "--partition_name",
735 partition_name]
736 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500737 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400738 if args and args.strip():
739 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700740 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500741
742 img.seek(os.SEEK_SET, 0)
743 data = img.read()
744
745 if has_ramdisk:
746 ramdisk_img.close()
747 img.close()
748
749 return data
750
751
Doug Zongkerd5131602012-08-02 14:46:42 -0700752def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800753 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700754 """Return a File object with the desired bootable image.
755
756 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
757 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
758 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700759
Doug Zongker55d93282011-01-25 17:03:34 -0800760 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
761 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700762 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800763 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700764
765 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
766 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700767 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700768 return File.FromLocalFile(name, prebuilt_path)
769
Tao Bao32fcdab2018-10-12 10:30:39 -0700770 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700771
772 if info_dict is None:
773 info_dict = OPTIONS.info_dict
774
775 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800776 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
777 # for recovery.
778 has_ramdisk = (info_dict.get("system_root_image") != "true" or
779 prebuilt_name != "boot.img" or
780 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700781
Doug Zongker6f1d0312014-08-22 08:07:12 -0700782 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400783 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
784 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800785 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700786 if data:
787 return File(name, data)
788 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800789
Doug Zongkereef39442009-04-02 12:14:19 -0700790
Narayan Kamatha07bf042017-08-14 14:49:21 +0100791def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800792 """Gunzips the given gzip compressed file to a given output file."""
793 with gzip.open(in_filename, "rb") as in_file, \
794 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100795 shutil.copyfileobj(in_file, out_file)
796
797
Tao Bao0ff15de2019-03-20 11:26:06 -0700798def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800799 """Unzips the archive to the given directory.
800
801 Args:
802 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800803 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700804 patterns: Files to unzip from the archive. If omitted, will unzip the entire
805 archvie. Non-matching patterns will be filtered out. If there's no match
806 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800807 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800808 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700809 if patterns is not None:
810 # Filter out non-matching patterns. unzip will complain otherwise.
811 with zipfile.ZipFile(filename) as input_zip:
812 names = input_zip.namelist()
813 filtered = [
814 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
815
816 # There isn't any matching files. Don't unzip anything.
817 if not filtered:
818 return
819 cmd.extend(filtered)
820
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800821 RunAndCheckOutput(cmd)
822
823
Doug Zongker75f17362009-12-08 13:46:44 -0800824def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800825 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800826
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800827 Args:
828 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
829 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
830
831 pattern: Files to unzip from the archive. If omitted, will unzip the entire
832 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800833
Tao Bao1c830bf2017-12-25 10:43:47 -0800834 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800835 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800836 """
Doug Zongkereef39442009-04-02 12:14:19 -0700837
Tao Bao1c830bf2017-12-25 10:43:47 -0800838 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800839 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
840 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800841 UnzipToDir(m.group(1), tmp, pattern)
842 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800843 filename = m.group(1)
844 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800845 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800846
Tao Baodba59ee2018-01-09 13:21:02 -0800847 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700848
849
Yifan Hong8a66a712019-04-04 15:37:57 -0700850def GetUserImage(which, tmpdir, input_zip,
851 info_dict=None,
852 allow_shared_blocks=None,
853 hashtree_info_generator=None,
854 reset_file_map=False):
855 """Returns an Image object suitable for passing to BlockImageDiff.
856
857 This function loads the specified image from the given path. If the specified
858 image is sparse, it also performs additional processing for OTA purpose. For
859 example, it always adds block 0 to clobbered blocks list. It also detects
860 files that cannot be reconstructed from the block list, for whom we should
861 avoid applying imgdiff.
862
863 Args:
864 which: The partition name.
865 tmpdir: The directory that contains the prebuilt image and block map file.
866 input_zip: The target-files ZIP archive.
867 info_dict: The dict to be looked up for relevant info.
868 allow_shared_blocks: If image is sparse, whether having shared blocks is
869 allowed. If none, it is looked up from info_dict.
870 hashtree_info_generator: If present and image is sparse, generates the
871 hashtree_info for this sparse image.
872 reset_file_map: If true and image is sparse, reset file map before returning
873 the image.
874 Returns:
875 A Image object. If it is a sparse image and reset_file_map is False, the
876 image will have file_map info loaded.
877 """
Tao Baoc1a1ec32019-06-18 16:29:37 -0700878 if info_dict is None:
Yifan Hong8a66a712019-04-04 15:37:57 -0700879 info_dict = LoadInfoDict(input_zip)
880
881 is_sparse = info_dict.get("extfs_sparse_flag")
882
883 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
884 # shared blocks (i.e. some blocks will show up in multiple files' block
885 # list). We can only allocate such shared blocks to the first "owner", and
886 # disable imgdiff for all later occurrences.
887 if allow_shared_blocks is None:
888 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
889
890 if is_sparse:
891 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
892 hashtree_info_generator)
893 if reset_file_map:
894 img.ResetFileMap()
895 return img
896 else:
897 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
898
899
900def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
901 """Returns a Image object suitable for passing to BlockImageDiff.
902
903 This function loads the specified non-sparse image from the given path.
904
905 Args:
906 which: The partition name.
907 tmpdir: The directory that contains the prebuilt image and block map file.
908 Returns:
909 A Image object.
910 """
911 path = os.path.join(tmpdir, "IMAGES", which + ".img")
912 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
913
914 # The image and map files must have been created prior to calling
915 # ota_from_target_files.py (since LMP).
916 assert os.path.exists(path) and os.path.exists(mappath)
917
918 return blockimgdiff.FileImage(path, hashtree_info_generator=
919 hashtree_info_generator)
920
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700921def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
922 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800923 """Returns a SparseImage object suitable for passing to BlockImageDiff.
924
925 This function loads the specified sparse image from the given path, and
926 performs additional processing for OTA purpose. For example, it always adds
927 block 0 to clobbered blocks list. It also detects files that cannot be
928 reconstructed from the block list, for whom we should avoid applying imgdiff.
929
930 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700931 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800932 tmpdir: The directory that contains the prebuilt image and block map file.
933 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800934 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700935 hashtree_info_generator: If present, generates the hashtree_info for this
936 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800937 Returns:
938 A SparseImage object, with file_map info loaded.
939 """
Tao Baoc765cca2018-01-31 17:32:40 -0800940 path = os.path.join(tmpdir, "IMAGES", which + ".img")
941 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
942
943 # The image and map files must have been created prior to calling
944 # ota_from_target_files.py (since LMP).
945 assert os.path.exists(path) and os.path.exists(mappath)
946
947 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
948 # it to clobbered_blocks so that it will be written to the target
949 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
950 clobbered_blocks = "0"
951
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700952 image = sparse_img.SparseImage(
953 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
954 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800955
956 # block.map may contain less blocks, because mke2fs may skip allocating blocks
957 # if they contain all zeros. We can't reconstruct such a file from its block
958 # list. Tag such entries accordingly. (Bug: 65213616)
959 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800960 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700961 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800962 continue
963
Tom Cherryd14b8952018-08-09 14:26:00 -0700964 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
965 # filename listed in system.map may contain an additional leading slash
966 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
967 # results.
Tao Baoda30cfa2017-12-01 16:19:46 -0800968 arcname = entry.replace(which, which.upper(), 1).lstrip('/')
Tao Baod3554e62018-07-10 15:31:22 -0700969
Tom Cherryd14b8952018-08-09 14:26:00 -0700970 # Special handling another case, where files not under /system
971 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700972 if which == 'system' and not arcname.startswith('SYSTEM'):
973 arcname = 'ROOT/' + arcname
974
975 assert arcname in input_zip.namelist(), \
976 "Failed to find the ZIP entry for {}".format(entry)
977
Tao Baoc765cca2018-01-31 17:32:40 -0800978 info = input_zip.getinfo(arcname)
979 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800980
981 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800982 # image, check the original block list to determine its completeness. Note
983 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800984 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800985 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800986
Tao Baoc765cca2018-01-31 17:32:40 -0800987 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
988 ranges.extra['incomplete'] = True
989
990 return image
991
992
Doug Zongkereef39442009-04-02 12:14:19 -0700993def GetKeyPasswords(keylist):
994 """Given a list of keys, prompt the user to enter passwords for
995 those which require them. Return a {key: password} dict. password
996 will be None if the key has no password."""
997
Doug Zongker8ce7c252009-05-22 13:34:54 -0700998 no_passwords = []
999 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -07001000 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -07001001 devnull = open("/dev/null", "w+b")
1002 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001003 # We don't need a password for things that aren't really keys.
1004 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001005 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -07001006 continue
1007
T.R. Fullhart37e10522013-03-18 10:31:26 -07001008 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -07001009 "-inform", "DER", "-nocrypt"],
1010 stdin=devnull.fileno(),
1011 stdout=devnull.fileno(),
1012 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001013 p.communicate()
1014 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001015 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001016 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001017 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001018 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1019 "-inform", "DER", "-passin", "pass:"],
1020 stdin=devnull.fileno(),
1021 stdout=devnull.fileno(),
1022 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001023 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001024 if p.returncode == 0:
1025 # Encrypted key with empty string as password.
1026 key_passwords[k] = ''
1027 elif stderr.startswith('Error decrypting key'):
1028 # Definitely encrypted key.
1029 # It would have said "Error reading key" if it didn't parse correctly.
1030 need_passwords.append(k)
1031 else:
1032 # Potentially, a type of key that openssl doesn't understand.
1033 # We'll let the routines in signapk.jar handle it.
1034 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001035 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001036
T.R. Fullhart37e10522013-03-18 10:31:26 -07001037 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001038 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001039 return key_passwords
1040
1041
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001042def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001043 """Gets the minSdkVersion declared in the APK.
1044
1045 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1046 This can be both a decimal number (API Level) or a codename.
1047
1048 Args:
1049 apk_name: The APK filename.
1050
1051 Returns:
1052 The parsed SDK version string.
1053
1054 Raises:
1055 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001056 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001057 proc = Run(
1058 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1059 stderr=subprocess.PIPE)
1060 stdoutdata, stderrdata = proc.communicate()
1061 if proc.returncode != 0:
1062 raise ExternalError(
1063 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1064 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001065
Tao Baof47bf0f2018-03-21 23:28:51 -07001066 for line in stdoutdata.split("\n"):
1067 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001068 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1069 if m:
1070 return m.group(1)
1071 raise ExternalError("No minSdkVersion returned by aapt")
1072
1073
1074def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001075 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001076
Tao Baof47bf0f2018-03-21 23:28:51 -07001077 If minSdkVersion is set to a codename, it is translated to a number using the
1078 provided map.
1079
1080 Args:
1081 apk_name: The APK filename.
1082
1083 Returns:
1084 The parsed SDK version number.
1085
1086 Raises:
1087 ExternalError: On failing to get the min SDK version number.
1088 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001089 version = GetMinSdkVersion(apk_name)
1090 try:
1091 return int(version)
1092 except ValueError:
1093 # Not a decimal number. Codename?
1094 if version in codename_to_api_level_map:
1095 return codename_to_api_level_map[version]
1096 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001097 raise ExternalError(
1098 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1099 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001100
1101
1102def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001103 codename_to_api_level_map=None, whole_file=False,
1104 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001105 """Sign the input_name zip/jar/apk, producing output_name. Use the
1106 given key and password (the latter may be None if the key does not
1107 have a password.
1108
Doug Zongker951495f2009-08-14 12:44:19 -07001109 If whole_file is true, use the "-w" option to SignApk to embed a
1110 signature that covers the whole file in the archive comment of the
1111 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001112
1113 min_api_level is the API Level (int) of the oldest platform this file may end
1114 up on. If not specified for an APK, the API Level is obtained by interpreting
1115 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1116
1117 codename_to_api_level_map is needed to translate the codename which may be
1118 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001119
1120 Caller may optionally specify extra args to be passed to SignApk, which
1121 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001122 """
Tao Bao76def242017-11-21 09:25:31 -08001123 if codename_to_api_level_map is None:
1124 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001125 if extra_signapk_args is None:
1126 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001127
Alex Klyubin9667b182015-12-10 13:38:50 -08001128 java_library_path = os.path.join(
1129 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1130
Tao Baoe95540e2016-11-08 12:08:53 -08001131 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1132 ["-Djava.library.path=" + java_library_path,
1133 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001134 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001135 if whole_file:
1136 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001137
1138 min_sdk_version = min_api_level
1139 if min_sdk_version is None:
1140 if not whole_file:
1141 min_sdk_version = GetMinSdkVersionInt(
1142 input_name, codename_to_api_level_map)
1143 if min_sdk_version is not None:
1144 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1145
T.R. Fullhart37e10522013-03-18 10:31:26 -07001146 cmd.extend([key + OPTIONS.public_key_suffix,
1147 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001148 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001149
Tao Bao73dd4f42018-10-04 16:25:33 -07001150 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001151 if password is not None:
1152 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001153 stdoutdata, _ = proc.communicate(password)
1154 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001155 raise ExternalError(
1156 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001157 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001158
Doug Zongkereef39442009-04-02 12:14:19 -07001159
Doug Zongker37974732010-09-16 17:44:38 -07001160def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001161 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001162
Tao Bao9dd909e2017-11-14 11:27:32 -08001163 For non-AVB images, raise exception if the data is too big. Print a warning
1164 if the data is nearing the maximum size.
1165
1166 For AVB images, the actual image size should be identical to the limit.
1167
1168 Args:
1169 data: A string that contains all the data for the partition.
1170 target: The partition name. The ".img" suffix is optional.
1171 info_dict: The dict to be looked up for relevant info.
1172 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001173 if target.endswith(".img"):
1174 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001175 mount_point = "/" + target
1176
Ying Wangf8824af2014-06-03 14:07:27 -07001177 fs_type = None
1178 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001179 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001180 if mount_point == "/userdata":
1181 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001182 p = info_dict["fstab"][mount_point]
1183 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001184 device = p.device
1185 if "/" in device:
1186 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001187 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001188 if not fs_type or not limit:
1189 return
Doug Zongkereef39442009-04-02 12:14:19 -07001190
Andrew Boie0f9aec82012-02-14 09:32:52 -08001191 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001192 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1193 # path.
1194 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1195 if size != limit:
1196 raise ExternalError(
1197 "Mismatching image size for %s: expected %d actual %d" % (
1198 target, limit, size))
1199 else:
1200 pct = float(size) * 100.0 / limit
1201 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1202 if pct >= 99.0:
1203 raise ExternalError(msg)
1204 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001205 logger.warning("\n WARNING: %s\n", msg)
1206 else:
1207 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001208
1209
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001210def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001211 """Parses the APK certs info from a given target-files zip.
1212
1213 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1214 tuple with the following elements: (1) a dictionary that maps packages to
1215 certs (based on the "certificate" and "private_key" attributes in the file;
1216 (2) a string representing the extension of compressed APKs in the target files
1217 (e.g ".gz", ".bro").
1218
1219 Args:
1220 tf_zip: The input target_files ZipFile (already open).
1221
1222 Returns:
1223 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1224 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1225 no compressed APKs.
1226 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001227 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001228 compressed_extension = None
1229
Tao Bao0f990332017-09-08 19:02:54 -07001230 # META/apkcerts.txt contains the info for _all_ the packages known at build
1231 # time. Filter out the ones that are not installed.
1232 installed_files = set()
1233 for name in tf_zip.namelist():
1234 basename = os.path.basename(name)
1235 if basename:
1236 installed_files.add(basename)
1237
Tao Baoda30cfa2017-12-01 16:19:46 -08001238 for line in tf_zip.read('META/apkcerts.txt').decode().split('\n'):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001239 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001240 if not line:
1241 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001242 m = re.match(
1243 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1244 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1245 line)
1246 if not m:
1247 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001248
Tao Bao818ddf52018-01-05 11:17:34 -08001249 matches = m.groupdict()
1250 cert = matches["CERT"]
1251 privkey = matches["PRIVKEY"]
1252 name = matches["NAME"]
1253 this_compressed_extension = matches["COMPRESSED"]
1254
1255 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1256 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1257 if cert in SPECIAL_CERT_STRINGS and not privkey:
1258 certmap[name] = cert
1259 elif (cert.endswith(OPTIONS.public_key_suffix) and
1260 privkey.endswith(OPTIONS.private_key_suffix) and
1261 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1262 certmap[name] = cert[:-public_key_suffix_len]
1263 else:
1264 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1265
1266 if not this_compressed_extension:
1267 continue
1268
1269 # Only count the installed files.
1270 filename = name + '.' + this_compressed_extension
1271 if filename not in installed_files:
1272 continue
1273
1274 # Make sure that all the values in the compression map have the same
1275 # extension. We don't support multiple compression methods in the same
1276 # system image.
1277 if compressed_extension:
1278 if this_compressed_extension != compressed_extension:
1279 raise ValueError(
1280 "Multiple compressed extensions: {} vs {}".format(
1281 compressed_extension, this_compressed_extension))
1282 else:
1283 compressed_extension = this_compressed_extension
1284
1285 return (certmap,
1286 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001287
1288
Doug Zongkereef39442009-04-02 12:14:19 -07001289COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001290Global options
1291
1292 -p (--path) <dir>
1293 Prepend <dir>/bin to the list of places to search for binaries run by this
1294 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001295
Doug Zongker05d3dea2009-06-22 11:32:31 -07001296 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001297 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001298
Tao Bao30df8b42018-04-23 15:32:53 -07001299 -x (--extra) <key=value>
1300 Add a key/value pair to the 'extras' dict, which device-specific extension
1301 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001302
Doug Zongkereef39442009-04-02 12:14:19 -07001303 -v (--verbose)
1304 Show command lines being executed.
1305
1306 -h (--help)
1307 Display this usage message and exit.
1308"""
1309
1310def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001311 print(docstring.rstrip("\n"))
1312 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001313
1314
1315def ParseOptions(argv,
1316 docstring,
1317 extra_opts="", extra_long_opts=(),
1318 extra_option_handler=None):
1319 """Parse the options in argv and return any arguments that aren't
1320 flags. docstring is the calling module's docstring, to be displayed
1321 for errors and -h. extra_opts and extra_long_opts are for flags
1322 defined by the caller, which are processed by passing them to
1323 extra_option_handler."""
1324
1325 try:
1326 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001327 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001328 ["help", "verbose", "path=", "signapk_path=",
1329 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001330 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001331 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1332 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001333 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001334 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001335 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001336 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001337 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001338 sys.exit(2)
1339
Doug Zongkereef39442009-04-02 12:14:19 -07001340 for o, a in opts:
1341 if o in ("-h", "--help"):
1342 Usage(docstring)
1343 sys.exit()
1344 elif o in ("-v", "--verbose"):
1345 OPTIONS.verbose = True
1346 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001347 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001348 elif o in ("--signapk_path",):
1349 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001350 elif o in ("--signapk_shared_library_path",):
1351 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001352 elif o in ("--extra_signapk_args",):
1353 OPTIONS.extra_signapk_args = shlex.split(a)
1354 elif o in ("--java_path",):
1355 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001356 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001357 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001358 elif o in ("--public_key_suffix",):
1359 OPTIONS.public_key_suffix = a
1360 elif o in ("--private_key_suffix",):
1361 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001362 elif o in ("--boot_signer_path",):
1363 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001364 elif o in ("--boot_signer_args",):
1365 OPTIONS.boot_signer_args = shlex.split(a)
1366 elif o in ("--verity_signer_path",):
1367 OPTIONS.verity_signer_path = a
1368 elif o in ("--verity_signer_args",):
1369 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001370 elif o in ("-s", "--device_specific"):
1371 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001372 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001373 key, value = a.split("=", 1)
1374 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001375 else:
1376 if extra_option_handler is None or not extra_option_handler(o, a):
1377 assert False, "unknown option \"%s\"" % (o,)
1378
Doug Zongker85448772014-09-09 14:59:20 -07001379 if OPTIONS.search_path:
1380 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1381 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001382
1383 return args
1384
1385
Tao Bao4c851b12016-09-19 13:54:38 -07001386def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001387 """Make a temp file and add it to the list of things to be deleted
1388 when Cleanup() is called. Return the filename."""
1389 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1390 os.close(fd)
1391 OPTIONS.tempfiles.append(fn)
1392 return fn
1393
1394
Tao Bao1c830bf2017-12-25 10:43:47 -08001395def MakeTempDir(prefix='tmp', suffix=''):
1396 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1397
1398 Returns:
1399 The absolute pathname of the new directory.
1400 """
1401 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1402 OPTIONS.tempfiles.append(dir_name)
1403 return dir_name
1404
1405
Doug Zongkereef39442009-04-02 12:14:19 -07001406def Cleanup():
1407 for i in OPTIONS.tempfiles:
1408 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001409 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001410 else:
1411 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001412 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001413
1414
1415class PasswordManager(object):
1416 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001417 self.editor = os.getenv("EDITOR")
1418 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001419
1420 def GetPasswords(self, items):
1421 """Get passwords corresponding to each string in 'items',
1422 returning a dict. (The dict may have keys in addition to the
1423 values in 'items'.)
1424
1425 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1426 user edit that file to add more needed passwords. If no editor is
1427 available, or $ANDROID_PW_FILE isn't define, prompts the user
1428 interactively in the ordinary way.
1429 """
1430
1431 current = self.ReadFile()
1432
1433 first = True
1434 while True:
1435 missing = []
1436 for i in items:
1437 if i not in current or not current[i]:
1438 missing.append(i)
1439 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001440 if not missing:
1441 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001442
1443 for i in missing:
1444 current[i] = ""
1445
1446 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001447 print("key file %s still missing some passwords." % (self.pwfile,))
Tao Baoda30cfa2017-12-01 16:19:46 -08001448 if sys.version_info[0] >= 3:
1449 raw_input = input # pylint: disable=redefined-builtin
Doug Zongker8ce7c252009-05-22 13:34:54 -07001450 answer = raw_input("try to edit again? [y]> ").strip()
1451 if answer and answer[0] not in 'yY':
1452 raise RuntimeError("key passwords unavailable")
1453 first = False
1454
1455 current = self.UpdateAndReadFile(current)
1456
Dan Albert8b72aef2015-03-23 19:13:21 -07001457 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001458 """Prompt the user to enter a value (password) for each key in
1459 'current' whose value is fales. Returns a new dict with all the
1460 values.
1461 """
1462 result = {}
Tao Bao38884282019-07-10 22:20:56 -07001463 for k, v in sorted(current.items()):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001464 if v:
1465 result[k] = v
1466 else:
1467 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001468 result[k] = getpass.getpass(
1469 "Enter password for %s key> " % k).strip()
1470 if result[k]:
1471 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001472 return result
1473
1474 def UpdateAndReadFile(self, current):
1475 if not self.editor or not self.pwfile:
1476 return self.PromptResult(current)
1477
1478 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001479 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001480 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1481 f.write("# (Additional spaces are harmless.)\n\n")
1482
1483 first_line = None
Tao Bao38884282019-07-10 22:20:56 -07001484 sorted_list = sorted([(not v, k, v) for (k, v) in current.items()])
Dan Albert8b72aef2015-03-23 19:13:21 -07001485 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001486 f.write("[[[ %s ]]] %s\n" % (v, k))
1487 if not v and first_line is None:
1488 # position cursor on first line with no password.
1489 first_line = i + 4
1490 f.close()
1491
Tao Bao986ee862018-10-04 15:46:16 -07001492 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001493
1494 return self.ReadFile()
1495
1496 def ReadFile(self):
1497 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001498 if self.pwfile is None:
1499 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001500 try:
1501 f = open(self.pwfile, "r")
1502 for line in f:
1503 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001504 if not line or line[0] == '#':
1505 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001506 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1507 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001508 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001509 else:
1510 result[m.group(2)] = m.group(1)
1511 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001512 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001513 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001514 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001515 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001516
1517
Dan Albert8e0178d2015-01-27 15:53:15 -08001518def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1519 compress_type=None):
1520 import datetime
1521
1522 # http://b/18015246
1523 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1524 # for files larger than 2GiB. We can work around this by adjusting their
1525 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1526 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1527 # it isn't clear to me exactly what circumstances cause this).
1528 # `zipfile.write()` must be used directly to work around this.
1529 #
1530 # This mess can be avoided if we port to python3.
1531 saved_zip64_limit = zipfile.ZIP64_LIMIT
1532 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1533
1534 if compress_type is None:
1535 compress_type = zip_file.compression
1536 if arcname is None:
1537 arcname = filename
1538
1539 saved_stat = os.stat(filename)
1540
1541 try:
1542 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1543 # file to be zipped and reset it when we're done.
1544 os.chmod(filename, perms)
1545
1546 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001547 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1548 # intentional. zip stores datetimes in local time without a time zone
1549 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1550 # in the zip archive.
1551 local_epoch = datetime.datetime.fromtimestamp(0)
1552 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001553 os.utime(filename, (timestamp, timestamp))
1554
1555 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1556 finally:
1557 os.chmod(filename, saved_stat.st_mode)
1558 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1559 zipfile.ZIP64_LIMIT = saved_zip64_limit
1560
1561
Tao Bao58c1b962015-05-20 09:32:18 -07001562def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001563 compress_type=None):
1564 """Wrap zipfile.writestr() function to work around the zip64 limit.
1565
1566 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1567 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1568 when calling crc32(bytes).
1569
1570 But it still works fine to write a shorter string into a large zip file.
1571 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1572 when we know the string won't be too long.
1573 """
1574
1575 saved_zip64_limit = zipfile.ZIP64_LIMIT
1576 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1577
1578 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1579 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001580 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001581 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001582 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001583 else:
Tao Baof3282b42015-04-01 11:21:55 -07001584 zinfo = zinfo_or_arcname
Tao Baoc1a1ec32019-06-18 16:29:37 -07001585 # Python 2 and 3 behave differently when calling ZipFile.writestr() with
1586 # zinfo.external_attr being 0. Python 3 uses `0o600 << 16` as the value for
1587 # such a case (since
1588 # https://github.com/python/cpython/commit/18ee29d0b870caddc0806916ca2c823254f1a1f9),
1589 # which seems to make more sense. Otherwise the entry will have 0o000 as the
1590 # permission bits. We follow the logic in Python 3 to get consistent
1591 # behavior between using the two versions.
1592 if not zinfo.external_attr:
1593 zinfo.external_attr = 0o600 << 16
Tao Baof3282b42015-04-01 11:21:55 -07001594
1595 # If compress_type is given, it overrides the value in zinfo.
1596 if compress_type is not None:
1597 zinfo.compress_type = compress_type
1598
Tao Bao58c1b962015-05-20 09:32:18 -07001599 # If perms is given, it has a priority.
1600 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001601 # If perms doesn't set the file type, mark it as a regular file.
1602 if perms & 0o770000 == 0:
1603 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001604 zinfo.external_attr = perms << 16
1605
Tao Baof3282b42015-04-01 11:21:55 -07001606 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001607 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1608
Dan Albert8b72aef2015-03-23 19:13:21 -07001609 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001610 zipfile.ZIP64_LIMIT = saved_zip64_limit
1611
1612
Tao Bao89d7ab22017-12-14 17:05:33 -08001613def ZipDelete(zip_filename, entries):
1614 """Deletes entries from a ZIP file.
1615
1616 Since deleting entries from a ZIP file is not supported, it shells out to
1617 'zip -d'.
1618
1619 Args:
1620 zip_filename: The name of the ZIP file.
1621 entries: The name of the entry, or the list of names to be deleted.
1622
1623 Raises:
1624 AssertionError: In case of non-zero return from 'zip'.
1625 """
Tao Baoc1a1ec32019-06-18 16:29:37 -07001626 if isinstance(entries, str):
Tao Bao89d7ab22017-12-14 17:05:33 -08001627 entries = [entries]
1628 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001629 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001630
1631
Tao Baof3282b42015-04-01 11:21:55 -07001632def ZipClose(zip_file):
1633 # http://b/18015246
1634 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1635 # central directory.
1636 saved_zip64_limit = zipfile.ZIP64_LIMIT
1637 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1638
1639 zip_file.close()
1640
1641 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001642
1643
1644class DeviceSpecificParams(object):
1645 module = None
1646 def __init__(self, **kwargs):
1647 """Keyword arguments to the constructor become attributes of this
1648 object, which is passed to all functions in the device-specific
1649 module."""
Tao Bao38884282019-07-10 22:20:56 -07001650 for k, v in kwargs.items():
Doug Zongker05d3dea2009-06-22 11:32:31 -07001651 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001652 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001653
1654 if self.module is None:
1655 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001656 if not path:
1657 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001658 try:
1659 if os.path.isdir(path):
1660 info = imp.find_module("releasetools", [path])
1661 else:
1662 d, f = os.path.split(path)
1663 b, x = os.path.splitext(f)
1664 if x == ".py":
1665 f = b
1666 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001667 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001668 self.module = imp.load_module("device_specific", *info)
1669 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001670 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001671
1672 def _DoCall(self, function_name, *args, **kwargs):
1673 """Call the named function in the device-specific module, passing
1674 the given args and kwargs. The first argument to the call will be
1675 the DeviceSpecific object itself. If there is no module, or the
1676 module does not define the function, return the value of the
1677 'default' kwarg (which itself defaults to None)."""
1678 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001679 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001680 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1681
1682 def FullOTA_Assertions(self):
1683 """Called after emitting the block of assertions at the top of a
1684 full OTA package. Implementations can add whatever additional
1685 assertions they like."""
1686 return self._DoCall("FullOTA_Assertions")
1687
Doug Zongkere5ff5902012-01-17 10:55:37 -08001688 def FullOTA_InstallBegin(self):
1689 """Called at the start of full OTA installation."""
1690 return self._DoCall("FullOTA_InstallBegin")
1691
Yifan Hong10c530d2018-12-27 17:34:18 -08001692 def FullOTA_GetBlockDifferences(self):
1693 """Called during full OTA installation and verification.
1694 Implementation should return a list of BlockDifference objects describing
1695 the update on each additional partitions.
1696 """
1697 return self._DoCall("FullOTA_GetBlockDifferences")
1698
Doug Zongker05d3dea2009-06-22 11:32:31 -07001699 def FullOTA_InstallEnd(self):
1700 """Called at the end of full OTA installation; typically this is
1701 used to install the image for the device's baseband processor."""
1702 return self._DoCall("FullOTA_InstallEnd")
1703
1704 def IncrementalOTA_Assertions(self):
1705 """Called after emitting the block of assertions at the top of an
1706 incremental OTA package. Implementations can add whatever
1707 additional assertions they like."""
1708 return self._DoCall("IncrementalOTA_Assertions")
1709
Doug Zongkere5ff5902012-01-17 10:55:37 -08001710 def IncrementalOTA_VerifyBegin(self):
1711 """Called at the start of the verification phase of incremental
1712 OTA installation; additional checks can be placed here to abort
1713 the script before any changes are made."""
1714 return self._DoCall("IncrementalOTA_VerifyBegin")
1715
Doug Zongker05d3dea2009-06-22 11:32:31 -07001716 def IncrementalOTA_VerifyEnd(self):
1717 """Called at the end of the verification phase of incremental OTA
1718 installation; additional checks can be placed here to abort the
1719 script before any changes are made."""
1720 return self._DoCall("IncrementalOTA_VerifyEnd")
1721
Doug Zongkere5ff5902012-01-17 10:55:37 -08001722 def IncrementalOTA_InstallBegin(self):
1723 """Called at the start of incremental OTA installation (after
1724 verification is complete)."""
1725 return self._DoCall("IncrementalOTA_InstallBegin")
1726
Yifan Hong10c530d2018-12-27 17:34:18 -08001727 def IncrementalOTA_GetBlockDifferences(self):
1728 """Called during incremental OTA installation and verification.
1729 Implementation should return a list of BlockDifference objects describing
1730 the update on each additional partitions.
1731 """
1732 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1733
Doug Zongker05d3dea2009-06-22 11:32:31 -07001734 def IncrementalOTA_InstallEnd(self):
1735 """Called at the end of incremental OTA installation; typically
1736 this is used to install the image for the device's baseband
1737 processor."""
1738 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001739
Tao Bao9bc6bb22015-11-09 16:58:28 -08001740 def VerifyOTA_Assertions(self):
1741 return self._DoCall("VerifyOTA_Assertions")
1742
Tao Bao76def242017-11-21 09:25:31 -08001743
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001744class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001745 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001746 self.name = name
1747 self.data = data
1748 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001749 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001750 self.sha1 = sha1(data).hexdigest()
1751
1752 @classmethod
1753 def FromLocalFile(cls, name, diskname):
1754 f = open(diskname, "rb")
1755 data = f.read()
1756 f.close()
1757 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001758
1759 def WriteToTemp(self):
1760 t = tempfile.NamedTemporaryFile()
1761 t.write(self.data)
1762 t.flush()
1763 return t
1764
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001765 def WriteToDir(self, d):
1766 with open(os.path.join(d, self.name), "wb") as fp:
1767 fp.write(self.data)
1768
Geremy Condra36bd3652014-02-06 19:45:10 -08001769 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001770 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001771
Tao Bao76def242017-11-21 09:25:31 -08001772
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001773DIFF_PROGRAM_BY_EXT = {
1774 ".gz" : "imgdiff",
1775 ".zip" : ["imgdiff", "-z"],
1776 ".jar" : ["imgdiff", "-z"],
1777 ".apk" : ["imgdiff", "-z"],
1778 ".img" : "imgdiff",
1779 }
1780
Tao Bao76def242017-11-21 09:25:31 -08001781
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001782class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001783 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001784 self.tf = tf
1785 self.sf = sf
1786 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001787 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001788
1789 def ComputePatch(self):
1790 """Compute the patch (as a string of data) needed to turn sf into
1791 tf. Returns the same tuple as GetPatch()."""
1792
1793 tf = self.tf
1794 sf = self.sf
1795
Doug Zongker24cd2802012-08-14 16:36:15 -07001796 if self.diff_program:
1797 diff_program = self.diff_program
1798 else:
1799 ext = os.path.splitext(tf.name)[1]
1800 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001801
1802 ttemp = tf.WriteToTemp()
1803 stemp = sf.WriteToTemp()
1804
1805 ext = os.path.splitext(tf.name)[1]
1806
1807 try:
1808 ptemp = tempfile.NamedTemporaryFile()
1809 if isinstance(diff_program, list):
1810 cmd = copy.copy(diff_program)
1811 else:
1812 cmd = [diff_program]
1813 cmd.append(stemp.name)
1814 cmd.append(ttemp.name)
1815 cmd.append(ptemp.name)
1816 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001817 err = []
1818 def run():
1819 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001820 if e:
1821 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001822 th = threading.Thread(target=run)
1823 th.start()
1824 th.join(timeout=300) # 5 mins
1825 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001826 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001827 p.terminate()
1828 th.join(5)
1829 if th.is_alive():
1830 p.kill()
1831 th.join()
1832
Tianjie Xua2a9f992018-01-05 15:15:54 -08001833 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001834 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001835 self.patch = None
1836 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001837 diff = ptemp.read()
1838 finally:
1839 ptemp.close()
1840 stemp.close()
1841 ttemp.close()
1842
1843 self.patch = diff
1844 return self.tf, self.sf, self.patch
1845
1846
1847 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001848 """Returns a tuple of (target_file, source_file, patch_data).
1849
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001850 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001851 computing the patch failed.
1852 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001853 return self.tf, self.sf, self.patch
1854
1855
1856def ComputeDifferences(diffs):
1857 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001858 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001859
1860 # Do the largest files first, to try and reduce the long-pole effect.
1861 by_size = [(i.tf.size, i) for i in diffs]
1862 by_size.sort(reverse=True)
1863 by_size = [i[1] for i in by_size]
1864
1865 lock = threading.Lock()
1866 diff_iter = iter(by_size) # accessed under lock
1867
1868 def worker():
1869 try:
1870 lock.acquire()
1871 for d in diff_iter:
1872 lock.release()
1873 start = time.time()
1874 d.ComputePatch()
1875 dur = time.time() - start
1876 lock.acquire()
1877
1878 tf, sf, patch = d.GetPatch()
1879 if sf.name == tf.name:
1880 name = tf.name
1881 else:
1882 name = "%s (%s)" % (tf.name, sf.name)
1883 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001884 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001885 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001886 logger.info(
1887 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1888 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001889 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001890 except Exception:
1891 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001892 raise
1893
1894 # start worker threads; wait for them all to finish.
1895 threads = [threading.Thread(target=worker)
1896 for i in range(OPTIONS.worker_threads)]
1897 for th in threads:
1898 th.start()
1899 while threads:
1900 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001901
1902
Dan Albert8b72aef2015-03-23 19:13:21 -07001903class BlockDifference(object):
1904 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001905 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001906 self.tgt = tgt
1907 self.src = src
1908 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001909 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001910 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001911
Tao Baodd2a5892015-03-12 12:32:37 -07001912 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001913 version = max(
1914 int(i) for i in
1915 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001916 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001917 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001918
1919 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001920 version=self.version,
1921 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001922 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001923 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001924 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001925 self.touched_src_ranges = b.touched_src_ranges
1926 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001927
Yifan Hong10c530d2018-12-27 17:34:18 -08001928 # On devices with dynamic partitions, for new partitions,
1929 # src is None but OPTIONS.source_info_dict is not.
1930 if OPTIONS.source_info_dict is None:
1931 is_dynamic_build = OPTIONS.info_dict.get(
1932 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001933 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001934 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001935 is_dynamic_build = OPTIONS.source_info_dict.get(
1936 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001937 is_dynamic_source = partition in shlex.split(
1938 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001939
Yifan Hongbb2658d2019-01-25 12:30:58 -08001940 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001941 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1942
Yifan Hongbb2658d2019-01-25 12:30:58 -08001943 # For dynamic partitions builds, check partition list in both source
1944 # and target build because new partitions may be added, and existing
1945 # partitions may be removed.
1946 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1947
Yifan Hong10c530d2018-12-27 17:34:18 -08001948 if is_dynamic:
1949 self.device = 'map_partition("%s")' % partition
1950 else:
1951 if OPTIONS.source_info_dict is None:
1952 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1953 else:
1954 _, device_path = GetTypeAndDevice("/" + partition,
1955 OPTIONS.source_info_dict)
1956 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001957
Tao Baod8d14be2016-02-04 14:26:02 -08001958 @property
1959 def required_cache(self):
1960 return self._required_cache
1961
Tao Bao76def242017-11-21 09:25:31 -08001962 def WriteScript(self, script, output_zip, progress=None,
1963 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001964 if not self.src:
1965 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001966 script.Print("Patching %s image unconditionally..." % (self.partition,))
1967 else:
1968 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001969
Dan Albert8b72aef2015-03-23 19:13:21 -07001970 if progress:
1971 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001972 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001973
1974 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001975 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001976
Tao Bao9bc6bb22015-11-09 16:58:28 -08001977 def WriteStrictVerifyScript(self, script):
1978 """Verify all the blocks in the care_map, including clobbered blocks.
1979
1980 This differs from the WriteVerifyScript() function: a) it prints different
1981 error messages; b) it doesn't allow half-way updated images to pass the
1982 verification."""
1983
1984 partition = self.partition
1985 script.Print("Verifying %s..." % (partition,))
1986 ranges = self.tgt.care_map
1987 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001988 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001989 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1990 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001991 self.device, ranges_str,
1992 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001993 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001994 script.AppendExtra("")
1995
Tao Baod522bdc2016-04-12 15:53:16 -07001996 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001997 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001998
1999 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08002000 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00002001 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07002002
2003 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002004 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08002005 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07002006 ranges = self.touched_src_ranges
2007 expected_sha1 = self.touched_src_sha1
2008 else:
2009 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
2010 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07002011
2012 # No blocks to be checked, skipping.
2013 if not ranges:
2014 return
2015
Tao Bao5ece99d2015-05-12 11:42:31 -07002016 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002017 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002018 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08002019 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
2020 '"%s.patch.dat")) then' % (
2021 self.device, ranges_str, expected_sha1,
2022 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002023 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002024 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002025
Tianjie Xufc3422a2015-12-15 11:53:59 -08002026 if self.version >= 4:
2027
2028 # Bug: 21124327
2029 # When generating incrementals for the system and vendor partitions in
2030 # version 4 or newer, explicitly check the first block (which contains
2031 # the superblock) of the partition to see if it's what we expect. If
2032 # this check fails, give an explicit log message about the partition
2033 # having been remounted R/W (the most likely explanation).
2034 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002035 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002036
2037 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002038 if partition == "system":
2039 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2040 else:
2041 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002042 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002043 'ifelse (block_image_recover({device}, "{ranges}") && '
2044 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002045 'package_extract_file("{partition}.transfer.list"), '
2046 '"{partition}.new.dat", "{partition}.patch.dat"), '
2047 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002048 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002049 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002050 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002051
Tao Baodd2a5892015-03-12 12:32:37 -07002052 # Abort the OTA update. Note that the incremental OTA cannot be applied
2053 # even if it may match the checksum of the target partition.
2054 # a) If version < 3, operations like move and erase will make changes
2055 # unconditionally and damage the partition.
2056 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002057 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002058 if partition == "system":
2059 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2060 else:
2061 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2062 script.AppendExtra((
2063 'abort("E%d: %s partition has unexpected contents");\n'
2064 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002065
Yifan Hong10c530d2018-12-27 17:34:18 -08002066 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002067 partition = self.partition
2068 script.Print('Verifying the updated %s image...' % (partition,))
2069 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2070 ranges = self.tgt.care_map
2071 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002072 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002073 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002074 self.device, ranges_str,
2075 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002076
2077 # Bug: 20881595
2078 # Verify that extended blocks are really zeroed out.
2079 if self.tgt.extended:
2080 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002081 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002082 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002083 self.device, ranges_str,
2084 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002085 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002086 if partition == "system":
2087 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2088 else:
2089 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002090 script.AppendExtra(
2091 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002092 ' abort("E%d: %s partition has unexpected non-zero contents after '
2093 'OTA update");\n'
2094 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002095 else:
2096 script.Print('Verified the updated %s image.' % (partition,))
2097
Tianjie Xu209db462016-05-24 17:34:52 -07002098 if partition == "system":
2099 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2100 else:
2101 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2102
Tao Bao5fcaaef2015-06-01 13:40:49 -07002103 script.AppendExtra(
2104 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002105 ' abort("E%d: %s partition has unexpected contents after OTA '
2106 'update");\n'
2107 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002108
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002109 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002110 ZipWrite(output_zip,
2111 '{}.transfer.list'.format(self.path),
2112 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002113
Tao Bao76def242017-11-21 09:25:31 -08002114 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2115 # its size. Quailty 9 almost triples the compression time but doesn't
2116 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002117 # zip | brotli(quality 6) | brotli(quality 9)
2118 # compressed_size: 942M | 869M (~8% reduced) | 854M
2119 # compression_time: 75s | 265s | 719s
2120 # decompression_time: 15s | 25s | 25s
2121
2122 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002123 brotli_cmd = ['brotli', '--quality=6',
2124 '--output={}.new.dat.br'.format(self.path),
2125 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002126 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002127 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002128
2129 new_data_name = '{}.new.dat.br'.format(self.partition)
2130 ZipWrite(output_zip,
2131 '{}.new.dat.br'.format(self.path),
2132 new_data_name,
2133 compress_type=zipfile.ZIP_STORED)
2134 else:
2135 new_data_name = '{}.new.dat'.format(self.partition)
2136 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2137
Dan Albert8e0178d2015-01-27 15:53:15 -08002138 ZipWrite(output_zip,
2139 '{}.patch.dat'.format(self.path),
2140 '{}.patch.dat'.format(self.partition),
2141 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002142
Tianjie Xu209db462016-05-24 17:34:52 -07002143 if self.partition == "system":
2144 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2145 else:
2146 code = ErrorCode.VENDOR_UPDATE_FAILURE
2147
Yifan Hong10c530d2018-12-27 17:34:18 -08002148 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002149 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002150 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002151 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002152 device=self.device, partition=self.partition,
2153 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002154 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002155
Dan Albert8b72aef2015-03-23 19:13:21 -07002156 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002157 data = source.ReadRangeSet(ranges)
2158 ctx = sha1()
2159
2160 for p in data:
2161 ctx.update(p)
2162
2163 return ctx.hexdigest()
2164
Tao Baoe9b61912015-07-09 17:37:49 -07002165 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2166 """Return the hash value for all zero blocks."""
2167 zero_block = '\x00' * 4096
2168 ctx = sha1()
2169 for _ in range(num_blocks):
2170 ctx.update(zero_block)
2171
2172 return ctx.hexdigest()
2173
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002174
2175DataImage = blockimgdiff.DataImage
Yifan Hong8a66a712019-04-04 15:37:57 -07002176EmptyImage = blockimgdiff.EmptyImage
Tao Bao76def242017-11-21 09:25:31 -08002177
Doug Zongker96a57e72010-09-26 14:57:41 -07002178# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002179PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002180 "ext4": "EMMC",
2181 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002182 "f2fs": "EMMC",
2183 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002184}
Doug Zongker96a57e72010-09-26 14:57:41 -07002185
Tao Bao76def242017-11-21 09:25:31 -08002186
Doug Zongker96a57e72010-09-26 14:57:41 -07002187def GetTypeAndDevice(mount_point, info):
2188 fstab = info["fstab"]
2189 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002190 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2191 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002192 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002193 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002194
2195
2196def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002197 """Parses and converts a PEM-encoded certificate into DER-encoded.
2198
2199 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2200
2201 Returns:
Tao Baoda30cfa2017-12-01 16:19:46 -08002202 The decoded certificate bytes.
Tao Bao17e4e612018-02-16 17:12:54 -08002203 """
2204 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002205 save = False
2206 for line in data.split("\n"):
2207 if "--END CERTIFICATE--" in line:
2208 break
2209 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002210 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002211 if "--BEGIN CERTIFICATE--" in line:
2212 save = True
Tao Baoda30cfa2017-12-01 16:19:46 -08002213 cert = base64.b64decode("".join(cert_buffer))
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002214 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002215
Tao Bao04e1f012018-02-04 12:13:35 -08002216
2217def ExtractPublicKey(cert):
2218 """Extracts the public key (PEM-encoded) from the given certificate file.
2219
2220 Args:
2221 cert: The certificate filename.
2222
2223 Returns:
2224 The public key string.
2225
2226 Raises:
2227 AssertionError: On non-zero return from 'openssl'.
2228 """
2229 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2230 # While openssl 1.1 writes the key into the given filename followed by '-out',
2231 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2232 # stdout instead.
2233 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2234 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2235 pubkey, stderrdata = proc.communicate()
2236 assert proc.returncode == 0, \
2237 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2238 return pubkey
2239
2240
Tao Bao1ac886e2019-06-26 11:58:22 -07002241def ExtractAvbPublicKey(avbtool, key):
Tao Bao2cc0ca12019-03-15 10:44:43 -07002242 """Extracts the AVB public key from the given public or private key.
2243
2244 Args:
Tao Bao1ac886e2019-06-26 11:58:22 -07002245 avbtool: The AVB tool to use.
Tao Bao2cc0ca12019-03-15 10:44:43 -07002246 key: The input key file, which should be PEM-encoded public or private key.
2247
2248 Returns:
2249 The path to the extracted AVB public key file.
2250 """
2251 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2252 RunAndCheckOutput(
Tao Bao1ac886e2019-06-26 11:58:22 -07002253 [avbtool, 'extract_public_key', "--key", key, "--output", output])
Tao Bao2cc0ca12019-03-15 10:44:43 -07002254 return output
2255
2256
Doug Zongker412c02f2014-02-13 10:58:24 -08002257def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2258 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002259 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002260
Tao Bao6d5d6232018-03-09 17:04:42 -08002261 Most of the space in the boot and recovery images is just the kernel, which is
2262 identical for the two, so the resulting patch should be efficient. Add it to
2263 the output zip, along with a shell script that is run from init.rc on first
2264 boot to actually do the patching and install the new recovery image.
2265
2266 Args:
2267 input_dir: The top-level input directory of the target-files.zip.
2268 output_sink: The callback function that writes the result.
2269 recovery_img: File object for the recovery image.
2270 boot_img: File objects for the boot image.
2271 info_dict: A dict returned by common.LoadInfoDict() on the input
2272 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002273 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002274 if info_dict is None:
2275 info_dict = OPTIONS.info_dict
2276
Tao Bao6d5d6232018-03-09 17:04:42 -08002277 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002278
Tao Baof2cffbd2015-07-22 12:33:18 -07002279 if full_recovery_image:
2280 output_sink("etc/recovery.img", recovery_img.data)
2281
2282 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002283 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002284 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002285 # With system-root-image, boot and recovery images will have mismatching
2286 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2287 # to handle such a case.
2288 if system_root_image:
2289 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002290 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002291 assert not os.path.exists(path)
2292 else:
2293 diff_program = ["imgdiff"]
2294 if os.path.exists(path):
2295 diff_program.append("-b")
2296 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002297 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002298 else:
2299 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002300
2301 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2302 _, _, patch = d.ComputePatch()
2303 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002304
Dan Albertebb19aa2015-03-27 19:11:53 -07002305 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002306 # The following GetTypeAndDevice()s need to use the path in the target
2307 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002308 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2309 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2310 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002311 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002312
Tao Baof2cffbd2015-07-22 12:33:18 -07002313 if full_recovery_image:
2314 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002315if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2316 applypatch \\
2317 --flash /system/etc/recovery.img \\
2318 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2319 log -t recovery "Installing new recovery image: succeeded" || \\
2320 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002321else
2322 log -t recovery "Recovery image already installed"
2323fi
2324""" % {'type': recovery_type,
2325 'device': recovery_device,
2326 'sha1': recovery_img.sha1,
2327 'size': recovery_img.size}
2328 else:
2329 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002330if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2331 applypatch %(bonus_args)s \\
2332 --patch /system/recovery-from-boot.p \\
2333 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2334 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2335 log -t recovery "Installing new recovery image: succeeded" || \\
2336 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002337else
2338 log -t recovery "Recovery image already installed"
2339fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002340""" % {'boot_size': boot_img.size,
2341 'boot_sha1': boot_img.sha1,
2342 'recovery_size': recovery_img.size,
2343 'recovery_sha1': recovery_img.sha1,
2344 'boot_type': boot_type,
2345 'boot_device': boot_device,
2346 'recovery_type': recovery_type,
2347 'recovery_device': recovery_device,
2348 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002349
2350 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002351 # in the L release.
2352 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002353
Tao Bao32fcdab2018-10-12 10:30:39 -07002354 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002355
Tao Baoda30cfa2017-12-01 16:19:46 -08002356 output_sink(sh_location, sh.encode())
Yifan Hong10c530d2018-12-27 17:34:18 -08002357
2358
2359class DynamicPartitionUpdate(object):
2360 def __init__(self, src_group=None, tgt_group=None, progress=None,
2361 block_difference=None):
2362 self.src_group = src_group
2363 self.tgt_group = tgt_group
2364 self.progress = progress
2365 self.block_difference = block_difference
2366
2367 @property
2368 def src_size(self):
2369 if not self.block_difference:
2370 return 0
2371 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2372
2373 @property
2374 def tgt_size(self):
2375 if not self.block_difference:
2376 return 0
2377 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2378
2379 @staticmethod
2380 def _GetSparseImageSize(img):
2381 if not img:
2382 return 0
2383 return img.blocksize * img.total_blocks
2384
2385
2386class DynamicGroupUpdate(object):
2387 def __init__(self, src_size=None, tgt_size=None):
2388 # None: group does not exist. 0: no size limits.
2389 self.src_size = src_size
2390 self.tgt_size = tgt_size
2391
2392
2393class DynamicPartitionsDifference(object):
2394 def __init__(self, info_dict, block_diffs, progress_dict=None,
2395 source_info_dict=None):
2396 if progress_dict is None:
Tao Baof1113e92019-06-18 12:10:14 -07002397 progress_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002398
2399 self._remove_all_before_apply = False
2400 if source_info_dict is None:
2401 self._remove_all_before_apply = True
Tao Baof1113e92019-06-18 12:10:14 -07002402 source_info_dict = {}
Yifan Hong10c530d2018-12-27 17:34:18 -08002403
Tao Baof1113e92019-06-18 12:10:14 -07002404 block_diff_dict = collections.OrderedDict(
2405 [(e.partition, e) for e in block_diffs])
2406
Yifan Hong10c530d2018-12-27 17:34:18 -08002407 assert len(block_diff_dict) == len(block_diffs), \
2408 "Duplicated BlockDifference object for {}".format(
2409 [partition for partition, count in
2410 collections.Counter(e.partition for e in block_diffs).items()
2411 if count > 1])
2412
Yifan Hong79997e52019-01-23 16:56:19 -08002413 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002414
2415 for p, block_diff in block_diff_dict.items():
2416 self._partition_updates[p] = DynamicPartitionUpdate()
2417 self._partition_updates[p].block_difference = block_diff
2418
2419 for p, progress in progress_dict.items():
2420 if p in self._partition_updates:
2421 self._partition_updates[p].progress = progress
2422
2423 tgt_groups = shlex.split(info_dict.get(
2424 "super_partition_groups", "").strip())
2425 src_groups = shlex.split(source_info_dict.get(
2426 "super_partition_groups", "").strip())
2427
2428 for g in tgt_groups:
2429 for p in shlex.split(info_dict.get(
2430 "super_%s_partition_list" % g, "").strip()):
2431 assert p in self._partition_updates, \
2432 "{} is in target super_{}_partition_list but no BlockDifference " \
2433 "object is provided.".format(p, g)
2434 self._partition_updates[p].tgt_group = g
2435
2436 for g in src_groups:
2437 for p in shlex.split(source_info_dict.get(
2438 "super_%s_partition_list" % g, "").strip()):
2439 assert p in self._partition_updates, \
2440 "{} is in source super_{}_partition_list but no BlockDifference " \
2441 "object is provided.".format(p, g)
2442 self._partition_updates[p].src_group = g
2443
Yifan Hong45433e42019-01-18 13:55:25 -08002444 target_dynamic_partitions = set(shlex.split(info_dict.get(
2445 "dynamic_partition_list", "").strip()))
2446 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2447 if u.tgt_size)
2448 assert block_diffs_with_target == target_dynamic_partitions, \
2449 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2450 list(target_dynamic_partitions), list(block_diffs_with_target))
2451
2452 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2453 "dynamic_partition_list", "").strip()))
2454 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2455 if u.src_size)
2456 assert block_diffs_with_source == source_dynamic_partitions, \
2457 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2458 list(source_dynamic_partitions), list(block_diffs_with_source))
2459
Yifan Hong10c530d2018-12-27 17:34:18 -08002460 if self._partition_updates:
2461 logger.info("Updating dynamic partitions %s",
2462 self._partition_updates.keys())
2463
Yifan Hong79997e52019-01-23 16:56:19 -08002464 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002465
2466 for g in tgt_groups:
2467 self._group_updates[g] = DynamicGroupUpdate()
2468 self._group_updates[g].tgt_size = int(info_dict.get(
2469 "super_%s_group_size" % g, "0").strip())
2470
2471 for g in src_groups:
2472 if g not in self._group_updates:
2473 self._group_updates[g] = DynamicGroupUpdate()
2474 self._group_updates[g].src_size = int(source_info_dict.get(
2475 "super_%s_group_size" % g, "0").strip())
2476
2477 self._Compute()
2478
2479 def WriteScript(self, script, output_zip, write_verify_script=False):
2480 script.Comment('--- Start patching dynamic partitions ---')
2481 for p, u in self._partition_updates.items():
2482 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2483 script.Comment('Patch partition %s' % p)
2484 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2485 write_verify_script=False)
2486
2487 op_list_path = MakeTempFile()
2488 with open(op_list_path, 'w') as f:
2489 for line in self._op_list:
2490 f.write('{}\n'.format(line))
2491
2492 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2493
2494 script.Comment('Update dynamic partition metadata')
2495 script.AppendExtra('assert(update_dynamic_partitions('
2496 'package_extract_file("dynamic_partitions_op_list")));')
2497
2498 if write_verify_script:
2499 for p, u in self._partition_updates.items():
2500 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2501 u.block_difference.WritePostInstallVerifyScript(script)
2502 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2503
2504 for p, u in self._partition_updates.items():
2505 if u.tgt_size and u.src_size <= u.tgt_size:
2506 script.Comment('Patch partition %s' % p)
2507 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2508 write_verify_script=write_verify_script)
2509 if write_verify_script:
2510 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2511
2512 script.Comment('--- End patching dynamic partitions ---')
2513
2514 def _Compute(self):
2515 self._op_list = list()
2516
2517 def append(line):
2518 self._op_list.append(line)
2519
2520 def comment(line):
2521 self._op_list.append("# %s" % line)
2522
2523 if self._remove_all_before_apply:
2524 comment('Remove all existing dynamic partitions and groups before '
2525 'applying full OTA')
2526 append('remove_all_groups')
2527
2528 for p, u in self._partition_updates.items():
2529 if u.src_group and not u.tgt_group:
2530 append('remove %s' % p)
2531
2532 for p, u in self._partition_updates.items():
2533 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2534 comment('Move partition %s from %s to default' % (p, u.src_group))
2535 append('move %s default' % p)
2536
2537 for p, u in self._partition_updates.items():
2538 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2539 comment('Shrink partition %s from %d to %d' %
2540 (p, u.src_size, u.tgt_size))
2541 append('resize %s %s' % (p, u.tgt_size))
2542
2543 for g, u in self._group_updates.items():
2544 if u.src_size is not None and u.tgt_size is None:
2545 append('remove_group %s' % g)
2546 if (u.src_size is not None and u.tgt_size is not None and
2547 u.src_size > u.tgt_size):
2548 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2549 append('resize_group %s %d' % (g, u.tgt_size))
2550
2551 for g, u in self._group_updates.items():
2552 if u.src_size is None and u.tgt_size is not None:
2553 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2554 append('add_group %s %d' % (g, u.tgt_size))
2555 if (u.src_size is not None and u.tgt_size is not None and
2556 u.src_size < u.tgt_size):
2557 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2558 append('resize_group %s %d' % (g, u.tgt_size))
2559
2560 for p, u in self._partition_updates.items():
2561 if u.tgt_group and not u.src_group:
2562 comment('Add partition %s to group %s' % (p, u.tgt_group))
2563 append('add %s %s' % (p, u.tgt_group))
2564
2565 for p, u in self._partition_updates.items():
2566 if u.tgt_size and u.src_size < u.tgt_size:
2567 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2568 append('resize %s %d' % (p, u.tgt_size))
2569
2570 for p, u in self._partition_updates.items():
2571 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2572 comment('Move partition %s from default to %s' %
2573 (p, u.tgt_group))
2574 append('move %s %s' % (p, u.tgt_group))