blob: 80f80029f0a11dd90ab4fd4d5f60c134f8d0d8d5 [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
Yifan Hong10c530d2018-12-27 17:34:18 -080017import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070018import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070019import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070020import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070021import getopt
22import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010023import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070024import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070025import json
26import logging
27import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070028import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080029import platform
Doug Zongkereef39442009-04-02 12:14:19 -070030import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070031import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070032import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080033import string
Doug Zongkereef39442009-04-02 12:14:19 -070034import 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
Dan Albert8b72aef2015-03-23 19:13:21 -070057 platform_search_path = {
Pavel Salomatov32676552019-03-06 20:00:45 +030058 "linux2": os.path.join(base_search_path, "host/linux-x86"),
59 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070060 }
Doug Zongker85448772014-09-09 14:59:20 -070061
Tao Bao76def242017-11-21 09:25:31 -080062 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070063 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080064 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070065 self.extra_signapk_args = []
66 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080067 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070068 self.public_key_suffix = ".x509.pem"
69 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070070 # use otatools built boot_signer by default
71 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070072 self.boot_signer_args = []
73 self.verity_signer_path = None
74 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.verbose = False
76 self.tempfiles = []
77 self.device_specific = None
78 self.extras = {}
79 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070080 self.source_info_dict = None
81 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070082 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070083 # Stash size cannot exceed cache_size * threshold.
84 self.cache_size = None
85 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070086
87
88OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070089
Tao Bao71197512018-10-11 14:08:45 -070090# The block size that's used across the releasetools scripts.
91BLOCK_SIZE = 4096
92
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080093# Values for "certificate" in apkcerts that mean special things.
94SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
95
Tao Bao5cc0abb2019-03-21 10:18:05 -070096# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
97# that system_other is not in the list because we don't want to include its
98# descriptor into vbmeta.img.
99AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'product_services',
100 'recovery', 'system', 'vendor')
Tao Bao9dd909e2017-11-14 11:27:32 -0800101
Tao Bao08c190f2019-06-03 23:07:58 -0700102# Chained VBMeta partitions.
103AVB_VBMETA_PARTITIONS = ('vbmeta_system', 'vbmeta_vendor')
104
Tianjie Xu861f4132018-09-12 11:49:33 -0700105# Partitions that should have their care_map added to META/care_map.pb
106PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
107 'odm')
108
109
Tianjie Xu209db462016-05-24 17:34:52 -0700110class ErrorCode(object):
111 """Define error_codes for failures that happen during the actual
112 update package installation.
113
114 Error codes 0-999 are reserved for failures before the package
115 installation (i.e. low battery, package verification failure).
116 Detailed code in 'bootable/recovery/error_code.h' """
117
118 SYSTEM_VERIFICATION_FAILURE = 1000
119 SYSTEM_UPDATE_FAILURE = 1001
120 SYSTEM_UNEXPECTED_CONTENTS = 1002
121 SYSTEM_NONZERO_CONTENTS = 1003
122 SYSTEM_RECOVER_FAILURE = 1004
123 VENDOR_VERIFICATION_FAILURE = 2000
124 VENDOR_UPDATE_FAILURE = 2001
125 VENDOR_UNEXPECTED_CONTENTS = 2002
126 VENDOR_NONZERO_CONTENTS = 2003
127 VENDOR_RECOVER_FAILURE = 2004
128 OEM_PROP_MISMATCH = 3000
129 FINGERPRINT_MISMATCH = 3001
130 THUMBPRINT_MISMATCH = 3002
131 OLDER_BUILD = 3003
132 DEVICE_MISMATCH = 3004
133 BAD_PATCH_FILE = 3005
134 INSUFFICIENT_CACHE_SPACE = 3006
135 TUNE_PARTITION_FAILURE = 3007
136 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800137
Tao Bao80921982018-03-21 21:02:19 -0700138
Dan Albert8b72aef2015-03-23 19:13:21 -0700139class ExternalError(RuntimeError):
140 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700141
142
Tao Bao32fcdab2018-10-12 10:30:39 -0700143def InitLogging():
144 DEFAULT_LOGGING_CONFIG = {
145 'version': 1,
146 'disable_existing_loggers': False,
147 'formatters': {
148 'standard': {
149 'format':
150 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
151 'datefmt': '%Y-%m-%d %H:%M:%S',
152 },
153 },
154 'handlers': {
155 'default': {
156 'class': 'logging.StreamHandler',
157 'formatter': 'standard',
158 },
159 },
160 'loggers': {
161 '': {
162 'handlers': ['default'],
163 'level': 'WARNING',
164 'propagate': True,
165 }
166 }
167 }
168 env_config = os.getenv('LOGGING_CONFIG')
169 if env_config:
170 with open(env_config) as f:
171 config = json.load(f)
172 else:
173 config = DEFAULT_LOGGING_CONFIG
174
175 # Increase the logging level for verbose mode.
176 if OPTIONS.verbose:
177 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
178 config['loggers']['']['level'] = 'INFO'
179
180 logging.config.dictConfig(config)
181
182
Tao Bao39451582017-05-04 11:10:47 -0700183def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700184 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700185
Tao Bao73dd4f42018-10-04 16:25:33 -0700186 Args:
187 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700188 verbose: Whether the commands should be shown. Default to the global
189 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700190 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
191 stdin, etc. stdout and stderr will default to subprocess.PIPE and
192 subprocess.STDOUT respectively unless caller specifies any of them.
193
194 Returns:
195 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700196 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700197 if 'stdout' not in kwargs and 'stderr' not in kwargs:
198 kwargs['stdout'] = subprocess.PIPE
199 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700200 # Don't log any if caller explicitly says so.
201 if verbose != False:
202 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700203 return subprocess.Popen(args, **kwargs)
204
205
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800206def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800207 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800208
209 Args:
210 args: The command represented as a list of strings.
211 verbose: Whether the commands should be shown. Default to the global
212 verbosity if unspecified.
213 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
214 stdin, etc. stdout and stderr will default to subprocess.PIPE and
215 subprocess.STDOUT respectively unless caller specifies any of them.
216
Bill Peckham889b0c62019-02-21 18:53:37 -0800217 Raises:
218 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800219 """
220 proc = Run(args, verbose=verbose, **kwargs)
221 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800222
223 if proc.returncode != 0:
224 raise ExternalError(
225 "Failed to run command '{}' (exit code {})".format(
226 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800227
228
Tao Bao986ee862018-10-04 15:46:16 -0700229def RunAndCheckOutput(args, verbose=None, **kwargs):
230 """Runs the given command and returns the output.
231
232 Args:
233 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700234 verbose: Whether the commands should be shown. Default to the global
235 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700236 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
237 stdin, etc. stdout and stderr will default to subprocess.PIPE and
238 subprocess.STDOUT respectively unless caller specifies any of them.
239
240 Returns:
241 The output string.
242
243 Raises:
244 ExternalError: On non-zero exit from the command.
245 """
Tao Bao986ee862018-10-04 15:46:16 -0700246 proc = Run(args, verbose=verbose, **kwargs)
247 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700248 # Don't log any if caller explicitly says so.
249 if verbose != False:
250 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700251 if proc.returncode != 0:
252 raise ExternalError(
253 "Failed to run command '{}' (exit code {}):\n{}".format(
254 args, proc.returncode, output))
255 return output
256
257
Tao Baoc765cca2018-01-31 17:32:40 -0800258def RoundUpTo4K(value):
259 rounded_up = value + 4095
260 return rounded_up - (rounded_up % 4096)
261
262
Ying Wang7e6d4e42010-12-13 16:25:36 -0800263def CloseInheritedPipes():
264 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
265 before doing other work."""
266 if platform.system() != "Darwin":
267 return
268 for d in range(3, 1025):
269 try:
270 stat = os.fstat(d)
271 if stat is not None:
272 pipebit = stat[0] & 0x1000
273 if pipebit != 0:
274 os.close(d)
275 except OSError:
276 pass
277
278
Tao Bao410ad8b2018-08-24 12:08:38 -0700279def LoadInfoDict(input_file, repacking=False):
280 """Loads the key/value pairs from the given input target_files.
281
282 It reads `META/misc_info.txt` file in the target_files input, does sanity
283 checks and returns the parsed key/value pairs for to the given build. It's
284 usually called early when working on input target_files files, e.g. when
285 generating OTAs, or signing builds. Note that the function may be called
286 against an old target_files file (i.e. from past dessert releases). So the
287 property parsing needs to be backward compatible.
288
289 In a `META/misc_info.txt`, a few properties are stored as links to the files
290 in the PRODUCT_OUT directory. It works fine with the build system. However,
291 they are no longer available when (re)generating images from target_files zip.
292 When `repacking` is True, redirect these properties to the actual files in the
293 unzipped directory.
294
295 Args:
296 input_file: The input target_files file, which could be an open
297 zipfile.ZipFile instance, or a str for the dir that contains the files
298 unzipped from a target_files file.
299 repacking: Whether it's trying repack an target_files file after loading the
300 info dict (default: False). If so, it will rewrite a few loaded
301 properties (e.g. selinux_fc, root_dir) to point to the actual files in
302 target_files file. When doing repacking, `input_file` must be a dir.
303
304 Returns:
305 A dict that contains the parsed key/value pairs.
306
307 Raises:
308 AssertionError: On invalid input arguments.
309 ValueError: On malformed input values.
310 """
311 if repacking:
312 assert isinstance(input_file, str), \
313 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700314
Doug Zongkerc9253822014-02-04 12:17:58 -0800315 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700316 if isinstance(input_file, zipfile.ZipFile):
317 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800318 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700319 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800320 try:
321 with open(path) as f:
322 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700323 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800324 if e.errno == errno.ENOENT:
325 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800326
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700327 try:
Michael Runge6e836112014-04-15 17:40:21 -0700328 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700329 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700330 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700331
Tao Bao410ad8b2018-08-24 12:08:38 -0700332 if "recovery_api_version" not in d:
333 raise ValueError("Failed to find 'recovery_api_version'")
334 if "fstab_version" not in d:
335 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800336
Tao Bao410ad8b2018-08-24 12:08:38 -0700337 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700338 # "selinux_fc" properties should point to the file_contexts files
339 # (file_contexts.bin) under META/.
340 for key in d:
341 if key.endswith("selinux_fc"):
342 fc_basename = os.path.basename(d[key])
343 fc_config = os.path.join(input_file, "META", fc_basename)
344 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700345
Daniel Norman72c626f2019-05-13 15:58:14 -0700346 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700347
Tom Cherryd14b8952018-08-09 14:26:00 -0700348 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700349 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700350 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700351 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700352
Tao Baof54216f2016-03-29 15:12:37 -0700353 # Redirect {system,vendor}_base_fs_file.
354 if "system_base_fs_file" in d:
355 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700356 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700357 if os.path.exists(system_base_fs_file):
358 d["system_base_fs_file"] = system_base_fs_file
359 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700360 logger.warning(
361 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700362 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700363
364 if "vendor_base_fs_file" in d:
365 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700366 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700367 if os.path.exists(vendor_base_fs_file):
368 d["vendor_base_fs_file"] = vendor_base_fs_file
369 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700370 logger.warning(
371 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700372 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700373
Doug Zongker37974732010-09-16 17:44:38 -0700374 def makeint(key):
375 if key in d:
376 d[key] = int(d[key], 0)
377
378 makeint("recovery_api_version")
379 makeint("blocksize")
380 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700381 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700382 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700383 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700384 makeint("recovery_size")
385 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800386 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700387
Tao Baoa57ab9f2018-08-24 12:08:38 -0700388 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
389 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
390 # cases, since it may load the info_dict from an old build (e.g. when
391 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800392 system_root_image = d.get("system_root_image") == "true"
393 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700394 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700395 if isinstance(input_file, zipfile.ZipFile):
396 if recovery_fstab_path not in input_file.namelist():
397 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
398 else:
399 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
400 if not os.path.exists(path):
401 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800402 d["fstab"] = LoadRecoveryFSTab(
403 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700404
Tao Bao76def242017-11-21 09:25:31 -0800405 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700406 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700407 if isinstance(input_file, zipfile.ZipFile):
408 if recovery_fstab_path not in input_file.namelist():
409 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
410 else:
411 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
412 if not os.path.exists(path):
413 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800414 d["fstab"] = LoadRecoveryFSTab(
415 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700416
Tianjie Xucfa86222016-03-07 16:31:19 -0800417 else:
418 d["fstab"] = None
419
Tianjie Xu861f4132018-09-12 11:49:33 -0700420 # Tries to load the build props for all partitions with care_map, including
421 # system and vendor.
422 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800423 partition_prop = "{}.build.prop".format(partition)
424 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700425 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800426 # Some partition might use /<partition>/etc/build.prop as the new path.
427 # TODO: try new path first when majority of them switch to the new path.
428 if not d[partition_prop]:
429 d[partition_prop] = LoadBuildProp(
430 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700431 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800432
433 # Set up the salt (based on fingerprint or thumbprint) that will be used when
434 # adding AVB footer.
435 if d.get("avb_enable") == "true":
436 fp = None
437 if "build.prop" in d:
438 build_prop = d["build.prop"]
439 if "ro.build.fingerprint" in build_prop:
440 fp = build_prop["ro.build.fingerprint"]
441 elif "ro.build.thumbprint" in build_prop:
442 fp = build_prop["ro.build.thumbprint"]
443 if fp:
444 d["avb_salt"] = sha256(fp).hexdigest()
445
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700446 return d
447
Tao Baod1de6f32017-03-01 16:38:48 -0800448
Tao Baobcd1d162017-08-26 13:10:26 -0700449def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700450 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700451 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700452 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700453 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700454 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700455 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700456
Tao Baod1de6f32017-03-01 16:38:48 -0800457
Michael Runge6e836112014-04-15 17:40:21 -0700458def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700459 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700460 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700461 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700462 if not line or line.startswith("#"):
463 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700464 if "=" in line:
465 name, value = line.split("=", 1)
466 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700467 return d
468
Tao Baod1de6f32017-03-01 16:38:48 -0800469
Tianjie Xucfa86222016-03-07 16:31:19 -0800470def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
471 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700472 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800473 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700474 self.mount_point = mount_point
475 self.fs_type = fs_type
476 self.device = device
477 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700478 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700479
480 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800481 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700482 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700483 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700484 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700485
Tao Baod1de6f32017-03-01 16:38:48 -0800486 assert fstab_version == 2
487
488 d = {}
489 for line in data.split("\n"):
490 line = line.strip()
491 if not line or line.startswith("#"):
492 continue
493
494 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
495 pieces = line.split()
496 if len(pieces) != 5:
497 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
498
499 # Ignore entries that are managed by vold.
500 options = pieces[4]
501 if "voldmanaged=" in options:
502 continue
503
504 # It's a good line, parse it.
505 length = 0
506 options = options.split(",")
507 for i in options:
508 if i.startswith("length="):
509 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800510 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800511 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700512 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800513
Tao Baod1de6f32017-03-01 16:38:48 -0800514 mount_flags = pieces[3]
515 # Honor the SELinux context if present.
516 context = None
517 for i in mount_flags.split(","):
518 if i.startswith("context="):
519 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800520
Tao Baod1de6f32017-03-01 16:38:48 -0800521 mount_point = pieces[1]
522 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
523 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800524
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700525 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700526 # system. Other areas assume system is always at "/system" so point /system
527 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700528 if system_root_image:
529 assert not d.has_key("/system") and d.has_key("/")
530 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700531 return d
532
533
Doug Zongker37974732010-09-16 17:44:38 -0700534def DumpInfoDict(d):
535 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700536 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700537
Dan Albert8b72aef2015-03-23 19:13:21 -0700538
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800539def AppendAVBSigningArgs(cmd, partition):
540 """Append signing arguments for avbtool."""
541 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
542 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
543 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
544 if key_path and algorithm:
545 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700546 avb_salt = OPTIONS.info_dict.get("avb_salt")
547 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700548 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700549 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800550
551
Tao Bao02a08592018-07-22 12:40:45 -0700552def GetAvbChainedPartitionArg(partition, info_dict, key=None):
553 """Constructs and returns the arg to build or verify a chained partition.
554
555 Args:
556 partition: The partition name.
557 info_dict: The info dict to look up the key info and rollback index
558 location.
559 key: The key to be used for building or verifying the partition. Defaults to
560 the key listed in info_dict.
561
562 Returns:
563 A string of form "partition:rollback_index_location:key" that can be used to
564 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700565 """
566 if key is None:
567 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao2cc0ca12019-03-15 10:44:43 -0700568 pubkey_path = ExtractAvbPublicKey(key)
Tao Bao02a08592018-07-22 12:40:45 -0700569 rollback_index_location = info_dict[
570 "avb_" + partition + "_rollback_index_location"]
571 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
572
573
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700574def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800575 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700576 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700577
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700578 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800579 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
580 we are building a two-step special image (i.e. building a recovery image to
581 be loaded into /boot in two-step OTAs).
582
583 Return the image data, or None if sourcedir does not appear to contains files
584 for building the requested image.
585 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700586
587 def make_ramdisk():
588 ramdisk_img = tempfile.NamedTemporaryFile()
589
590 if os.access(fs_config_file, os.F_OK):
591 cmd = ["mkbootfs", "-f", fs_config_file,
592 os.path.join(sourcedir, "RAMDISK")]
593 else:
594 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
595 p1 = Run(cmd, stdout=subprocess.PIPE)
596 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
597
598 p2.wait()
599 p1.wait()
600 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
601 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
602
603 return ramdisk_img
604
605 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
606 return None
607
608 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700609 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700610
Doug Zongkerd5131602012-08-02 14:46:42 -0700611 if info_dict is None:
612 info_dict = OPTIONS.info_dict
613
Doug Zongkereef39442009-04-02 12:14:19 -0700614 img = tempfile.NamedTemporaryFile()
615
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700616 if has_ramdisk:
617 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700618
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800619 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
620 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
621
622 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700623
Benoit Fradina45a8682014-07-14 21:00:43 +0200624 fn = os.path.join(sourcedir, "second")
625 if os.access(fn, os.F_OK):
626 cmd.append("--second")
627 cmd.append(fn)
628
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800629 fn = os.path.join(sourcedir, "dtb")
630 if os.access(fn, os.F_OK):
631 cmd.append("--dtb")
632 cmd.append(fn)
633
Doug Zongker171f1cd2009-06-15 22:36:37 -0700634 fn = os.path.join(sourcedir, "cmdline")
635 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700636 cmd.append("--cmdline")
637 cmd.append(open(fn).read().rstrip("\n"))
638
639 fn = os.path.join(sourcedir, "base")
640 if os.access(fn, os.F_OK):
641 cmd.append("--base")
642 cmd.append(open(fn).read().rstrip("\n"))
643
Ying Wang4de6b5b2010-08-25 14:29:34 -0700644 fn = os.path.join(sourcedir, "pagesize")
645 if os.access(fn, os.F_OK):
646 cmd.append("--pagesize")
647 cmd.append(open(fn).read().rstrip("\n"))
648
Tao Bao76def242017-11-21 09:25:31 -0800649 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700650 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700651 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700652
Tao Bao76def242017-11-21 09:25:31 -0800653 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000654 if args and args.strip():
655 cmd.extend(shlex.split(args))
656
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700657 if has_ramdisk:
658 cmd.extend(["--ramdisk", ramdisk_img.name])
659
Tao Baod95e9fd2015-03-29 23:07:41 -0700660 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800661 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700662 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700663 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700664 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700665 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700666
Tao Baobf70c3182017-07-11 17:27:55 -0700667 # "boot" or "recovery", without extension.
668 partition_name = os.path.basename(sourcedir).lower()
669
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800670 if partition_name == "recovery":
671 if info_dict.get("include_recovery_dtbo") == "true":
672 fn = os.path.join(sourcedir, "recovery_dtbo")
673 cmd.extend(["--recovery_dtbo", fn])
674 if info_dict.get("include_recovery_acpio") == "true":
675 fn = os.path.join(sourcedir, "recovery_acpio")
676 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700677
Tao Bao986ee862018-10-04 15:46:16 -0700678 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700679
Tao Bao76def242017-11-21 09:25:31 -0800680 if (info_dict.get("boot_signer") == "true" and
681 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800682 # Hard-code the path as "/boot" for two-step special recovery image (which
683 # will be loaded into /boot during the two-step OTA).
684 if two_step_image:
685 path = "/boot"
686 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700687 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700688 cmd = [OPTIONS.boot_signer_path]
689 cmd.extend(OPTIONS.boot_signer_args)
690 cmd.extend([path, img.name,
691 info_dict["verity_key"] + ".pk8",
692 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700693 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700694
Tao Baod95e9fd2015-03-29 23:07:41 -0700695 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800696 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700697 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700698 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800699 # We have switched from the prebuilt futility binary to using the tool
700 # (futility-host) built from the source. Override the setting in the old
701 # TF.zip.
702 futility = info_dict["futility"]
703 if futility.startswith("prebuilts/"):
704 futility = "futility-host"
705 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700706 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700707 info_dict["vboot_key"] + ".vbprivk",
708 info_dict["vboot_subkey"] + ".vbprivk",
709 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700710 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700711 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700712
Tao Baof3282b42015-04-01 11:21:55 -0700713 # Clean up the temp files.
714 img_unsigned.close()
715 img_keyblock.close()
716
David Zeuthen8fecb282017-12-01 16:24:01 -0500717 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800718 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700719 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500720 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400721 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700722 "--partition_size", str(part_size), "--partition_name",
723 partition_name]
724 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500725 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400726 if args and args.strip():
727 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700728 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500729
730 img.seek(os.SEEK_SET, 0)
731 data = img.read()
732
733 if has_ramdisk:
734 ramdisk_img.close()
735 img.close()
736
737 return data
738
739
Doug Zongkerd5131602012-08-02 14:46:42 -0700740def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800741 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700742 """Return a File object with the desired bootable image.
743
744 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
745 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
746 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700747
Doug Zongker55d93282011-01-25 17:03:34 -0800748 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
749 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700750 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800751 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700752
753 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
754 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700755 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700756 return File.FromLocalFile(name, prebuilt_path)
757
Tao Bao32fcdab2018-10-12 10:30:39 -0700758 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700759
760 if info_dict is None:
761 info_dict = OPTIONS.info_dict
762
763 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800764 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
765 # for recovery.
766 has_ramdisk = (info_dict.get("system_root_image") != "true" or
767 prebuilt_name != "boot.img" or
768 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700769
Doug Zongker6f1d0312014-08-22 08:07:12 -0700770 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400771 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
772 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800773 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700774 if data:
775 return File(name, data)
776 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800777
Doug Zongkereef39442009-04-02 12:14:19 -0700778
Narayan Kamatha07bf042017-08-14 14:49:21 +0100779def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800780 """Gunzips the given gzip compressed file to a given output file."""
781 with gzip.open(in_filename, "rb") as in_file, \
782 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100783 shutil.copyfileobj(in_file, out_file)
784
785
Tao Bao0ff15de2019-03-20 11:26:06 -0700786def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800787 """Unzips the archive to the given directory.
788
789 Args:
790 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800791 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700792 patterns: Files to unzip from the archive. If omitted, will unzip the entire
793 archvie. Non-matching patterns will be filtered out. If there's no match
794 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800795 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800796 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700797 if patterns is not None:
798 # Filter out non-matching patterns. unzip will complain otherwise.
799 with zipfile.ZipFile(filename) as input_zip:
800 names = input_zip.namelist()
801 filtered = [
802 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
803
804 # There isn't any matching files. Don't unzip anything.
805 if not filtered:
806 return
807 cmd.extend(filtered)
808
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800809 RunAndCheckOutput(cmd)
810
811
Doug Zongker75f17362009-12-08 13:46:44 -0800812def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800813 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800814
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800815 Args:
816 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
817 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
818
819 pattern: Files to unzip from the archive. If omitted, will unzip the entire
820 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800821
Tao Bao1c830bf2017-12-25 10:43:47 -0800822 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800823 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800824 """
Doug Zongkereef39442009-04-02 12:14:19 -0700825
Tao Bao1c830bf2017-12-25 10:43:47 -0800826 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800827 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
828 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800829 UnzipToDir(m.group(1), tmp, pattern)
830 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800831 filename = m.group(1)
832 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800833 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800834
Tao Baodba59ee2018-01-09 13:21:02 -0800835 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700836
837
Yifan Hong8a66a712019-04-04 15:37:57 -0700838def GetUserImage(which, tmpdir, input_zip,
839 info_dict=None,
840 allow_shared_blocks=None,
841 hashtree_info_generator=None,
842 reset_file_map=False):
843 """Returns an Image object suitable for passing to BlockImageDiff.
844
845 This function loads the specified image from the given path. If the specified
846 image is sparse, it also performs additional processing for OTA purpose. For
847 example, it always adds block 0 to clobbered blocks list. It also detects
848 files that cannot be reconstructed from the block list, for whom we should
849 avoid applying imgdiff.
850
851 Args:
852 which: The partition name.
853 tmpdir: The directory that contains the prebuilt image and block map file.
854 input_zip: The target-files ZIP archive.
855 info_dict: The dict to be looked up for relevant info.
856 allow_shared_blocks: If image is sparse, whether having shared blocks is
857 allowed. If none, it is looked up from info_dict.
858 hashtree_info_generator: If present and image is sparse, generates the
859 hashtree_info for this sparse image.
860 reset_file_map: If true and image is sparse, reset file map before returning
861 the image.
862 Returns:
863 A Image object. If it is a sparse image and reset_file_map is False, the
864 image will have file_map info loaded.
865 """
866 if info_dict == None:
867 info_dict = LoadInfoDict(input_zip)
868
869 is_sparse = info_dict.get("extfs_sparse_flag")
870
871 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
872 # shared blocks (i.e. some blocks will show up in multiple files' block
873 # list). We can only allocate such shared blocks to the first "owner", and
874 # disable imgdiff for all later occurrences.
875 if allow_shared_blocks is None:
876 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
877
878 if is_sparse:
879 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
880 hashtree_info_generator)
881 if reset_file_map:
882 img.ResetFileMap()
883 return img
884 else:
885 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
886
887
888def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
889 """Returns a Image object suitable for passing to BlockImageDiff.
890
891 This function loads the specified non-sparse image from the given path.
892
893 Args:
894 which: The partition name.
895 tmpdir: The directory that contains the prebuilt image and block map file.
896 Returns:
897 A Image object.
898 """
899 path = os.path.join(tmpdir, "IMAGES", which + ".img")
900 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
901
902 # The image and map files must have been created prior to calling
903 # ota_from_target_files.py (since LMP).
904 assert os.path.exists(path) and os.path.exists(mappath)
905
906 return blockimgdiff.FileImage(path, hashtree_info_generator=
907 hashtree_info_generator)
908
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700909def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
910 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800911 """Returns a SparseImage object suitable for passing to BlockImageDiff.
912
913 This function loads the specified sparse image from the given path, and
914 performs additional processing for OTA purpose. For example, it always adds
915 block 0 to clobbered blocks list. It also detects files that cannot be
916 reconstructed from the block list, for whom we should avoid applying imgdiff.
917
918 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700919 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800920 tmpdir: The directory that contains the prebuilt image and block map file.
921 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800922 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700923 hashtree_info_generator: If present, generates the hashtree_info for this
924 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800925 Returns:
926 A SparseImage object, with file_map info loaded.
927 """
Tao Baoc765cca2018-01-31 17:32:40 -0800928 path = os.path.join(tmpdir, "IMAGES", which + ".img")
929 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
930
931 # The image and map files must have been created prior to calling
932 # ota_from_target_files.py (since LMP).
933 assert os.path.exists(path) and os.path.exists(mappath)
934
935 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
936 # it to clobbered_blocks so that it will be written to the target
937 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
938 clobbered_blocks = "0"
939
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700940 image = sparse_img.SparseImage(
941 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
942 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800943
944 # block.map may contain less blocks, because mke2fs may skip allocating blocks
945 # if they contain all zeros. We can't reconstruct such a file from its block
946 # list. Tag such entries accordingly. (Bug: 65213616)
947 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800948 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700949 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800950 continue
951
Tom Cherryd14b8952018-08-09 14:26:00 -0700952 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
953 # filename listed in system.map may contain an additional leading slash
954 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
955 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700956 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
957
Tom Cherryd14b8952018-08-09 14:26:00 -0700958 # Special handling another case, where files not under /system
959 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700960 if which == 'system' and not arcname.startswith('SYSTEM'):
961 arcname = 'ROOT/' + arcname
962
963 assert arcname in input_zip.namelist(), \
964 "Failed to find the ZIP entry for {}".format(entry)
965
Tao Baoc765cca2018-01-31 17:32:40 -0800966 info = input_zip.getinfo(arcname)
967 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800968
969 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800970 # image, check the original block list to determine its completeness. Note
971 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800972 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800973 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800974
Tao Baoc765cca2018-01-31 17:32:40 -0800975 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
976 ranges.extra['incomplete'] = True
977
978 return image
979
980
Doug Zongkereef39442009-04-02 12:14:19 -0700981def GetKeyPasswords(keylist):
982 """Given a list of keys, prompt the user to enter passwords for
983 those which require them. Return a {key: password} dict. password
984 will be None if the key has no password."""
985
Doug Zongker8ce7c252009-05-22 13:34:54 -0700986 no_passwords = []
987 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700988 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700989 devnull = open("/dev/null", "w+b")
990 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800991 # We don't need a password for things that aren't really keys.
992 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700993 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700994 continue
995
T.R. Fullhart37e10522013-03-18 10:31:26 -0700996 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700997 "-inform", "DER", "-nocrypt"],
998 stdin=devnull.fileno(),
999 stdout=devnull.fileno(),
1000 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -07001001 p.communicate()
1002 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001003 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001004 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001005 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001006 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1007 "-inform", "DER", "-passin", "pass:"],
1008 stdin=devnull.fileno(),
1009 stdout=devnull.fileno(),
1010 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001011 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001012 if p.returncode == 0:
1013 # Encrypted key with empty string as password.
1014 key_passwords[k] = ''
1015 elif stderr.startswith('Error decrypting key'):
1016 # Definitely encrypted key.
1017 # It would have said "Error reading key" if it didn't parse correctly.
1018 need_passwords.append(k)
1019 else:
1020 # Potentially, a type of key that openssl doesn't understand.
1021 # We'll let the routines in signapk.jar handle it.
1022 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001023 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001024
T.R. Fullhart37e10522013-03-18 10:31:26 -07001025 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001026 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001027 return key_passwords
1028
1029
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001030def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001031 """Gets the minSdkVersion declared in the APK.
1032
1033 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1034 This can be both a decimal number (API Level) or a codename.
1035
1036 Args:
1037 apk_name: The APK filename.
1038
1039 Returns:
1040 The parsed SDK version string.
1041
1042 Raises:
1043 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001044 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001045 proc = Run(
1046 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1047 stderr=subprocess.PIPE)
1048 stdoutdata, stderrdata = proc.communicate()
1049 if proc.returncode != 0:
1050 raise ExternalError(
1051 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1052 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001053
Tao Baof47bf0f2018-03-21 23:28:51 -07001054 for line in stdoutdata.split("\n"):
1055 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001056 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1057 if m:
1058 return m.group(1)
1059 raise ExternalError("No minSdkVersion returned by aapt")
1060
1061
1062def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001063 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001064
Tao Baof47bf0f2018-03-21 23:28:51 -07001065 If minSdkVersion is set to a codename, it is translated to a number using the
1066 provided map.
1067
1068 Args:
1069 apk_name: The APK filename.
1070
1071 Returns:
1072 The parsed SDK version number.
1073
1074 Raises:
1075 ExternalError: On failing to get the min SDK version number.
1076 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001077 version = GetMinSdkVersion(apk_name)
1078 try:
1079 return int(version)
1080 except ValueError:
1081 # Not a decimal number. Codename?
1082 if version in codename_to_api_level_map:
1083 return codename_to_api_level_map[version]
1084 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001085 raise ExternalError(
1086 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1087 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001088
1089
1090def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001091 codename_to_api_level_map=None, whole_file=False,
1092 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001093 """Sign the input_name zip/jar/apk, producing output_name. Use the
1094 given key and password (the latter may be None if the key does not
1095 have a password.
1096
Doug Zongker951495f2009-08-14 12:44:19 -07001097 If whole_file is true, use the "-w" option to SignApk to embed a
1098 signature that covers the whole file in the archive comment of the
1099 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001100
1101 min_api_level is the API Level (int) of the oldest platform this file may end
1102 up on. If not specified for an APK, the API Level is obtained by interpreting
1103 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1104
1105 codename_to_api_level_map is needed to translate the codename which may be
1106 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001107
1108 Caller may optionally specify extra args to be passed to SignApk, which
1109 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001110 """
Tao Bao76def242017-11-21 09:25:31 -08001111 if codename_to_api_level_map is None:
1112 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001113 if extra_signapk_args is None:
1114 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001115
Alex Klyubin9667b182015-12-10 13:38:50 -08001116 java_library_path = os.path.join(
1117 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1118
Tao Baoe95540e2016-11-08 12:08:53 -08001119 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1120 ["-Djava.library.path=" + java_library_path,
1121 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001122 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001123 if whole_file:
1124 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001125
1126 min_sdk_version = min_api_level
1127 if min_sdk_version is None:
1128 if not whole_file:
1129 min_sdk_version = GetMinSdkVersionInt(
1130 input_name, codename_to_api_level_map)
1131 if min_sdk_version is not None:
1132 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1133
T.R. Fullhart37e10522013-03-18 10:31:26 -07001134 cmd.extend([key + OPTIONS.public_key_suffix,
1135 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001136 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001137
Tao Bao73dd4f42018-10-04 16:25:33 -07001138 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001139 if password is not None:
1140 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001141 stdoutdata, _ = proc.communicate(password)
1142 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001143 raise ExternalError(
1144 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001145 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001146
Doug Zongkereef39442009-04-02 12:14:19 -07001147
Doug Zongker37974732010-09-16 17:44:38 -07001148def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001149 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001150
Tao Bao9dd909e2017-11-14 11:27:32 -08001151 For non-AVB images, raise exception if the data is too big. Print a warning
1152 if the data is nearing the maximum size.
1153
1154 For AVB images, the actual image size should be identical to the limit.
1155
1156 Args:
1157 data: A string that contains all the data for the partition.
1158 target: The partition name. The ".img" suffix is optional.
1159 info_dict: The dict to be looked up for relevant info.
1160 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001161 if target.endswith(".img"):
1162 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001163 mount_point = "/" + target
1164
Ying Wangf8824af2014-06-03 14:07:27 -07001165 fs_type = None
1166 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001167 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001168 if mount_point == "/userdata":
1169 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001170 p = info_dict["fstab"][mount_point]
1171 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001172 device = p.device
1173 if "/" in device:
1174 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001175 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001176 if not fs_type or not limit:
1177 return
Doug Zongkereef39442009-04-02 12:14:19 -07001178
Andrew Boie0f9aec82012-02-14 09:32:52 -08001179 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001180 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1181 # path.
1182 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1183 if size != limit:
1184 raise ExternalError(
1185 "Mismatching image size for %s: expected %d actual %d" % (
1186 target, limit, size))
1187 else:
1188 pct = float(size) * 100.0 / limit
1189 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1190 if pct >= 99.0:
1191 raise ExternalError(msg)
1192 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001193 logger.warning("\n WARNING: %s\n", msg)
1194 else:
1195 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001196
1197
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001198def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001199 """Parses the APK certs info from a given target-files zip.
1200
1201 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1202 tuple with the following elements: (1) a dictionary that maps packages to
1203 certs (based on the "certificate" and "private_key" attributes in the file;
1204 (2) a string representing the extension of compressed APKs in the target files
1205 (e.g ".gz", ".bro").
1206
1207 Args:
1208 tf_zip: The input target_files ZipFile (already open).
1209
1210 Returns:
1211 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1212 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1213 no compressed APKs.
1214 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001215 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001216 compressed_extension = None
1217
Tao Bao0f990332017-09-08 19:02:54 -07001218 # META/apkcerts.txt contains the info for _all_ the packages known at build
1219 # time. Filter out the ones that are not installed.
1220 installed_files = set()
1221 for name in tf_zip.namelist():
1222 basename = os.path.basename(name)
1223 if basename:
1224 installed_files.add(basename)
1225
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001226 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1227 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001228 if not line:
1229 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001230 m = re.match(
1231 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1232 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1233 line)
1234 if not m:
1235 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001236
Tao Bao818ddf52018-01-05 11:17:34 -08001237 matches = m.groupdict()
1238 cert = matches["CERT"]
1239 privkey = matches["PRIVKEY"]
1240 name = matches["NAME"]
1241 this_compressed_extension = matches["COMPRESSED"]
1242
1243 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1244 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1245 if cert in SPECIAL_CERT_STRINGS and not privkey:
1246 certmap[name] = cert
1247 elif (cert.endswith(OPTIONS.public_key_suffix) and
1248 privkey.endswith(OPTIONS.private_key_suffix) and
1249 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1250 certmap[name] = cert[:-public_key_suffix_len]
1251 else:
1252 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1253
1254 if not this_compressed_extension:
1255 continue
1256
1257 # Only count the installed files.
1258 filename = name + '.' + this_compressed_extension
1259 if filename not in installed_files:
1260 continue
1261
1262 # Make sure that all the values in the compression map have the same
1263 # extension. We don't support multiple compression methods in the same
1264 # system image.
1265 if compressed_extension:
1266 if this_compressed_extension != compressed_extension:
1267 raise ValueError(
1268 "Multiple compressed extensions: {} vs {}".format(
1269 compressed_extension, this_compressed_extension))
1270 else:
1271 compressed_extension = this_compressed_extension
1272
1273 return (certmap,
1274 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001275
1276
Doug Zongkereef39442009-04-02 12:14:19 -07001277COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001278Global options
1279
1280 -p (--path) <dir>
1281 Prepend <dir>/bin to the list of places to search for binaries run by this
1282 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001283
Doug Zongker05d3dea2009-06-22 11:32:31 -07001284 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001285 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001286
Tao Bao30df8b42018-04-23 15:32:53 -07001287 -x (--extra) <key=value>
1288 Add a key/value pair to the 'extras' dict, which device-specific extension
1289 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001290
Doug Zongkereef39442009-04-02 12:14:19 -07001291 -v (--verbose)
1292 Show command lines being executed.
1293
1294 -h (--help)
1295 Display this usage message and exit.
1296"""
1297
1298def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001299 print(docstring.rstrip("\n"))
1300 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001301
1302
1303def ParseOptions(argv,
1304 docstring,
1305 extra_opts="", extra_long_opts=(),
1306 extra_option_handler=None):
1307 """Parse the options in argv and return any arguments that aren't
1308 flags. docstring is the calling module's docstring, to be displayed
1309 for errors and -h. extra_opts and extra_long_opts are for flags
1310 defined by the caller, which are processed by passing them to
1311 extra_option_handler."""
1312
1313 try:
1314 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001315 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001316 ["help", "verbose", "path=", "signapk_path=",
1317 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001318 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001319 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1320 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001321 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001322 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001323 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001324 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001325 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001326 sys.exit(2)
1327
Doug Zongkereef39442009-04-02 12:14:19 -07001328 for o, a in opts:
1329 if o in ("-h", "--help"):
1330 Usage(docstring)
1331 sys.exit()
1332 elif o in ("-v", "--verbose"):
1333 OPTIONS.verbose = True
1334 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001335 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001336 elif o in ("--signapk_path",):
1337 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001338 elif o in ("--signapk_shared_library_path",):
1339 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001340 elif o in ("--extra_signapk_args",):
1341 OPTIONS.extra_signapk_args = shlex.split(a)
1342 elif o in ("--java_path",):
1343 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001344 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001345 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001346 elif o in ("--public_key_suffix",):
1347 OPTIONS.public_key_suffix = a
1348 elif o in ("--private_key_suffix",):
1349 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001350 elif o in ("--boot_signer_path",):
1351 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001352 elif o in ("--boot_signer_args",):
1353 OPTIONS.boot_signer_args = shlex.split(a)
1354 elif o in ("--verity_signer_path",):
1355 OPTIONS.verity_signer_path = a
1356 elif o in ("--verity_signer_args",):
1357 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001358 elif o in ("-s", "--device_specific"):
1359 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001360 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001361 key, value = a.split("=", 1)
1362 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001363 else:
1364 if extra_option_handler is None or not extra_option_handler(o, a):
1365 assert False, "unknown option \"%s\"" % (o,)
1366
Doug Zongker85448772014-09-09 14:59:20 -07001367 if OPTIONS.search_path:
1368 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1369 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001370
1371 return args
1372
1373
Tao Bao4c851b12016-09-19 13:54:38 -07001374def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001375 """Make a temp file and add it to the list of things to be deleted
1376 when Cleanup() is called. Return the filename."""
1377 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1378 os.close(fd)
1379 OPTIONS.tempfiles.append(fn)
1380 return fn
1381
1382
Tao Bao1c830bf2017-12-25 10:43:47 -08001383def MakeTempDir(prefix='tmp', suffix=''):
1384 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1385
1386 Returns:
1387 The absolute pathname of the new directory.
1388 """
1389 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1390 OPTIONS.tempfiles.append(dir_name)
1391 return dir_name
1392
1393
Doug Zongkereef39442009-04-02 12:14:19 -07001394def Cleanup():
1395 for i in OPTIONS.tempfiles:
1396 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001397 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001398 else:
1399 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001400 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001401
1402
1403class PasswordManager(object):
1404 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001405 self.editor = os.getenv("EDITOR")
1406 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001407
1408 def GetPasswords(self, items):
1409 """Get passwords corresponding to each string in 'items',
1410 returning a dict. (The dict may have keys in addition to the
1411 values in 'items'.)
1412
1413 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1414 user edit that file to add more needed passwords. If no editor is
1415 available, or $ANDROID_PW_FILE isn't define, prompts the user
1416 interactively in the ordinary way.
1417 """
1418
1419 current = self.ReadFile()
1420
1421 first = True
1422 while True:
1423 missing = []
1424 for i in items:
1425 if i not in current or not current[i]:
1426 missing.append(i)
1427 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001428 if not missing:
1429 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001430
1431 for i in missing:
1432 current[i] = ""
1433
1434 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001435 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001436 answer = raw_input("try to edit again? [y]> ").strip()
1437 if answer and answer[0] not in 'yY':
1438 raise RuntimeError("key passwords unavailable")
1439 first = False
1440
1441 current = self.UpdateAndReadFile(current)
1442
Dan Albert8b72aef2015-03-23 19:13:21 -07001443 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001444 """Prompt the user to enter a value (password) for each key in
1445 'current' whose value is fales. Returns a new dict with all the
1446 values.
1447 """
1448 result = {}
1449 for k, v in sorted(current.iteritems()):
1450 if v:
1451 result[k] = v
1452 else:
1453 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001454 result[k] = getpass.getpass(
1455 "Enter password for %s key> " % k).strip()
1456 if result[k]:
1457 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001458 return result
1459
1460 def UpdateAndReadFile(self, current):
1461 if not self.editor or not self.pwfile:
1462 return self.PromptResult(current)
1463
1464 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001465 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001466 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1467 f.write("# (Additional spaces are harmless.)\n\n")
1468
1469 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001470 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1471 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001472 f.write("[[[ %s ]]] %s\n" % (v, k))
1473 if not v and first_line is None:
1474 # position cursor on first line with no password.
1475 first_line = i + 4
1476 f.close()
1477
Tao Bao986ee862018-10-04 15:46:16 -07001478 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001479
1480 return self.ReadFile()
1481
1482 def ReadFile(self):
1483 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001484 if self.pwfile is None:
1485 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001486 try:
1487 f = open(self.pwfile, "r")
1488 for line in f:
1489 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001490 if not line or line[0] == '#':
1491 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001492 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1493 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001494 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001495 else:
1496 result[m.group(2)] = m.group(1)
1497 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001498 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001499 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001500 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001501 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001502
1503
Dan Albert8e0178d2015-01-27 15:53:15 -08001504def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1505 compress_type=None):
1506 import datetime
1507
1508 # http://b/18015246
1509 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1510 # for files larger than 2GiB. We can work around this by adjusting their
1511 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1512 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1513 # it isn't clear to me exactly what circumstances cause this).
1514 # `zipfile.write()` must be used directly to work around this.
1515 #
1516 # This mess can be avoided if we port to python3.
1517 saved_zip64_limit = zipfile.ZIP64_LIMIT
1518 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1519
1520 if compress_type is None:
1521 compress_type = zip_file.compression
1522 if arcname is None:
1523 arcname = filename
1524
1525 saved_stat = os.stat(filename)
1526
1527 try:
1528 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1529 # file to be zipped and reset it when we're done.
1530 os.chmod(filename, perms)
1531
1532 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001533 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1534 # intentional. zip stores datetimes in local time without a time zone
1535 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1536 # in the zip archive.
1537 local_epoch = datetime.datetime.fromtimestamp(0)
1538 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001539 os.utime(filename, (timestamp, timestamp))
1540
1541 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1542 finally:
1543 os.chmod(filename, saved_stat.st_mode)
1544 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1545 zipfile.ZIP64_LIMIT = saved_zip64_limit
1546
1547
Tao Bao58c1b962015-05-20 09:32:18 -07001548def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001549 compress_type=None):
1550 """Wrap zipfile.writestr() function to work around the zip64 limit.
1551
1552 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1553 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1554 when calling crc32(bytes).
1555
1556 But it still works fine to write a shorter string into a large zip file.
1557 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1558 when we know the string won't be too long.
1559 """
1560
1561 saved_zip64_limit = zipfile.ZIP64_LIMIT
1562 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1563
1564 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1565 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001566 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001567 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001568 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001569 else:
Tao Baof3282b42015-04-01 11:21:55 -07001570 zinfo = zinfo_or_arcname
1571
1572 # If compress_type is given, it overrides the value in zinfo.
1573 if compress_type is not None:
1574 zinfo.compress_type = compress_type
1575
Tao Bao58c1b962015-05-20 09:32:18 -07001576 # If perms is given, it has a priority.
1577 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001578 # If perms doesn't set the file type, mark it as a regular file.
1579 if perms & 0o770000 == 0:
1580 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001581 zinfo.external_attr = perms << 16
1582
Tao Baof3282b42015-04-01 11:21:55 -07001583 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001584 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1585
Dan Albert8b72aef2015-03-23 19:13:21 -07001586 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001587 zipfile.ZIP64_LIMIT = saved_zip64_limit
1588
1589
Tao Bao89d7ab22017-12-14 17:05:33 -08001590def ZipDelete(zip_filename, entries):
1591 """Deletes entries from a ZIP file.
1592
1593 Since deleting entries from a ZIP file is not supported, it shells out to
1594 'zip -d'.
1595
1596 Args:
1597 zip_filename: The name of the ZIP file.
1598 entries: The name of the entry, or the list of names to be deleted.
1599
1600 Raises:
1601 AssertionError: In case of non-zero return from 'zip'.
1602 """
1603 if isinstance(entries, basestring):
1604 entries = [entries]
1605 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001606 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001607
1608
Tao Baof3282b42015-04-01 11:21:55 -07001609def ZipClose(zip_file):
1610 # http://b/18015246
1611 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1612 # central directory.
1613 saved_zip64_limit = zipfile.ZIP64_LIMIT
1614 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1615
1616 zip_file.close()
1617
1618 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001619
1620
1621class DeviceSpecificParams(object):
1622 module = None
1623 def __init__(self, **kwargs):
1624 """Keyword arguments to the constructor become attributes of this
1625 object, which is passed to all functions in the device-specific
1626 module."""
1627 for k, v in kwargs.iteritems():
1628 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001629 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001630
1631 if self.module is None:
1632 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001633 if not path:
1634 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001635 try:
1636 if os.path.isdir(path):
1637 info = imp.find_module("releasetools", [path])
1638 else:
1639 d, f = os.path.split(path)
1640 b, x = os.path.splitext(f)
1641 if x == ".py":
1642 f = b
1643 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001644 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001645 self.module = imp.load_module("device_specific", *info)
1646 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001647 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001648
1649 def _DoCall(self, function_name, *args, **kwargs):
1650 """Call the named function in the device-specific module, passing
1651 the given args and kwargs. The first argument to the call will be
1652 the DeviceSpecific object itself. If there is no module, or the
1653 module does not define the function, return the value of the
1654 'default' kwarg (which itself defaults to None)."""
1655 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001656 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001657 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1658
1659 def FullOTA_Assertions(self):
1660 """Called after emitting the block of assertions at the top of a
1661 full OTA package. Implementations can add whatever additional
1662 assertions they like."""
1663 return self._DoCall("FullOTA_Assertions")
1664
Doug Zongkere5ff5902012-01-17 10:55:37 -08001665 def FullOTA_InstallBegin(self):
1666 """Called at the start of full OTA installation."""
1667 return self._DoCall("FullOTA_InstallBegin")
1668
Yifan Hong10c530d2018-12-27 17:34:18 -08001669 def FullOTA_GetBlockDifferences(self):
1670 """Called during full OTA installation and verification.
1671 Implementation should return a list of BlockDifference objects describing
1672 the update on each additional partitions.
1673 """
1674 return self._DoCall("FullOTA_GetBlockDifferences")
1675
Doug Zongker05d3dea2009-06-22 11:32:31 -07001676 def FullOTA_InstallEnd(self):
1677 """Called at the end of full OTA installation; typically this is
1678 used to install the image for the device's baseband processor."""
1679 return self._DoCall("FullOTA_InstallEnd")
1680
1681 def IncrementalOTA_Assertions(self):
1682 """Called after emitting the block of assertions at the top of an
1683 incremental OTA package. Implementations can add whatever
1684 additional assertions they like."""
1685 return self._DoCall("IncrementalOTA_Assertions")
1686
Doug Zongkere5ff5902012-01-17 10:55:37 -08001687 def IncrementalOTA_VerifyBegin(self):
1688 """Called at the start of the verification phase of incremental
1689 OTA installation; additional checks can be placed here to abort
1690 the script before any changes are made."""
1691 return self._DoCall("IncrementalOTA_VerifyBegin")
1692
Doug Zongker05d3dea2009-06-22 11:32:31 -07001693 def IncrementalOTA_VerifyEnd(self):
1694 """Called at the end of the verification phase of incremental OTA
1695 installation; additional checks can be placed here to abort the
1696 script before any changes are made."""
1697 return self._DoCall("IncrementalOTA_VerifyEnd")
1698
Doug Zongkere5ff5902012-01-17 10:55:37 -08001699 def IncrementalOTA_InstallBegin(self):
1700 """Called at the start of incremental OTA installation (after
1701 verification is complete)."""
1702 return self._DoCall("IncrementalOTA_InstallBegin")
1703
Yifan Hong10c530d2018-12-27 17:34:18 -08001704 def IncrementalOTA_GetBlockDifferences(self):
1705 """Called during incremental OTA installation and verification.
1706 Implementation should return a list of BlockDifference objects describing
1707 the update on each additional partitions.
1708 """
1709 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1710
Doug Zongker05d3dea2009-06-22 11:32:31 -07001711 def IncrementalOTA_InstallEnd(self):
1712 """Called at the end of incremental OTA installation; typically
1713 this is used to install the image for the device's baseband
1714 processor."""
1715 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001716
Tao Bao9bc6bb22015-11-09 16:58:28 -08001717 def VerifyOTA_Assertions(self):
1718 return self._DoCall("VerifyOTA_Assertions")
1719
Tao Bao76def242017-11-21 09:25:31 -08001720
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001721class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001722 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001723 self.name = name
1724 self.data = data
1725 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001726 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001727 self.sha1 = sha1(data).hexdigest()
1728
1729 @classmethod
1730 def FromLocalFile(cls, name, diskname):
1731 f = open(diskname, "rb")
1732 data = f.read()
1733 f.close()
1734 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001735
1736 def WriteToTemp(self):
1737 t = tempfile.NamedTemporaryFile()
1738 t.write(self.data)
1739 t.flush()
1740 return t
1741
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001742 def WriteToDir(self, d):
1743 with open(os.path.join(d, self.name), "wb") as fp:
1744 fp.write(self.data)
1745
Geremy Condra36bd3652014-02-06 19:45:10 -08001746 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001747 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001748
Tao Bao76def242017-11-21 09:25:31 -08001749
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001750DIFF_PROGRAM_BY_EXT = {
1751 ".gz" : "imgdiff",
1752 ".zip" : ["imgdiff", "-z"],
1753 ".jar" : ["imgdiff", "-z"],
1754 ".apk" : ["imgdiff", "-z"],
1755 ".img" : "imgdiff",
1756 }
1757
Tao Bao76def242017-11-21 09:25:31 -08001758
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001759class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001760 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001761 self.tf = tf
1762 self.sf = sf
1763 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001764 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001765
1766 def ComputePatch(self):
1767 """Compute the patch (as a string of data) needed to turn sf into
1768 tf. Returns the same tuple as GetPatch()."""
1769
1770 tf = self.tf
1771 sf = self.sf
1772
Doug Zongker24cd2802012-08-14 16:36:15 -07001773 if self.diff_program:
1774 diff_program = self.diff_program
1775 else:
1776 ext = os.path.splitext(tf.name)[1]
1777 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001778
1779 ttemp = tf.WriteToTemp()
1780 stemp = sf.WriteToTemp()
1781
1782 ext = os.path.splitext(tf.name)[1]
1783
1784 try:
1785 ptemp = tempfile.NamedTemporaryFile()
1786 if isinstance(diff_program, list):
1787 cmd = copy.copy(diff_program)
1788 else:
1789 cmd = [diff_program]
1790 cmd.append(stemp.name)
1791 cmd.append(ttemp.name)
1792 cmd.append(ptemp.name)
1793 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001794 err = []
1795 def run():
1796 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001797 if e:
1798 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001799 th = threading.Thread(target=run)
1800 th.start()
1801 th.join(timeout=300) # 5 mins
1802 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001803 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001804 p.terminate()
1805 th.join(5)
1806 if th.is_alive():
1807 p.kill()
1808 th.join()
1809
Tianjie Xua2a9f992018-01-05 15:15:54 -08001810 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001811 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001812 self.patch = None
1813 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001814 diff = ptemp.read()
1815 finally:
1816 ptemp.close()
1817 stemp.close()
1818 ttemp.close()
1819
1820 self.patch = diff
1821 return self.tf, self.sf, self.patch
1822
1823
1824 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001825 """Returns a tuple of (target_file, source_file, patch_data).
1826
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001827 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001828 computing the patch failed.
1829 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001830 return self.tf, self.sf, self.patch
1831
1832
1833def ComputeDifferences(diffs):
1834 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001835 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001836
1837 # Do the largest files first, to try and reduce the long-pole effect.
1838 by_size = [(i.tf.size, i) for i in diffs]
1839 by_size.sort(reverse=True)
1840 by_size = [i[1] for i in by_size]
1841
1842 lock = threading.Lock()
1843 diff_iter = iter(by_size) # accessed under lock
1844
1845 def worker():
1846 try:
1847 lock.acquire()
1848 for d in diff_iter:
1849 lock.release()
1850 start = time.time()
1851 d.ComputePatch()
1852 dur = time.time() - start
1853 lock.acquire()
1854
1855 tf, sf, patch = d.GetPatch()
1856 if sf.name == tf.name:
1857 name = tf.name
1858 else:
1859 name = "%s (%s)" % (tf.name, sf.name)
1860 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001861 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001862 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001863 logger.info(
1864 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1865 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001866 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001867 except Exception:
1868 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001869 raise
1870
1871 # start worker threads; wait for them all to finish.
1872 threads = [threading.Thread(target=worker)
1873 for i in range(OPTIONS.worker_threads)]
1874 for th in threads:
1875 th.start()
1876 while threads:
1877 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001878
1879
Dan Albert8b72aef2015-03-23 19:13:21 -07001880class BlockDifference(object):
1881 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001882 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001883 self.tgt = tgt
1884 self.src = src
1885 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001886 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001887 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001888
Tao Baodd2a5892015-03-12 12:32:37 -07001889 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001890 version = max(
1891 int(i) for i in
1892 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001893 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001894 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001895
1896 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001897 version=self.version,
1898 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001899 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001900 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001901 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001902 self.touched_src_ranges = b.touched_src_ranges
1903 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001904
Yifan Hong10c530d2018-12-27 17:34:18 -08001905 # On devices with dynamic partitions, for new partitions,
1906 # src is None but OPTIONS.source_info_dict is not.
1907 if OPTIONS.source_info_dict is None:
1908 is_dynamic_build = OPTIONS.info_dict.get(
1909 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001910 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001911 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001912 is_dynamic_build = OPTIONS.source_info_dict.get(
1913 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001914 is_dynamic_source = partition in shlex.split(
1915 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001916
Yifan Hongbb2658d2019-01-25 12:30:58 -08001917 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001918 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1919
Yifan Hongbb2658d2019-01-25 12:30:58 -08001920 # For dynamic partitions builds, check partition list in both source
1921 # and target build because new partitions may be added, and existing
1922 # partitions may be removed.
1923 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1924
Yifan Hong10c530d2018-12-27 17:34:18 -08001925 if is_dynamic:
1926 self.device = 'map_partition("%s")' % partition
1927 else:
1928 if OPTIONS.source_info_dict is None:
1929 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1930 else:
1931 _, device_path = GetTypeAndDevice("/" + partition,
1932 OPTIONS.source_info_dict)
1933 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001934
Tao Baod8d14be2016-02-04 14:26:02 -08001935 @property
1936 def required_cache(self):
1937 return self._required_cache
1938
Tao Bao76def242017-11-21 09:25:31 -08001939 def WriteScript(self, script, output_zip, progress=None,
1940 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001941 if not self.src:
1942 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001943 script.Print("Patching %s image unconditionally..." % (self.partition,))
1944 else:
1945 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001946
Dan Albert8b72aef2015-03-23 19:13:21 -07001947 if progress:
1948 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001949 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001950
1951 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001952 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001953
Tao Bao9bc6bb22015-11-09 16:58:28 -08001954 def WriteStrictVerifyScript(self, script):
1955 """Verify all the blocks in the care_map, including clobbered blocks.
1956
1957 This differs from the WriteVerifyScript() function: a) it prints different
1958 error messages; b) it doesn't allow half-way updated images to pass the
1959 verification."""
1960
1961 partition = self.partition
1962 script.Print("Verifying %s..." % (partition,))
1963 ranges = self.tgt.care_map
1964 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001965 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001966 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1967 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001968 self.device, ranges_str,
1969 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001970 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001971 script.AppendExtra("")
1972
Tao Baod522bdc2016-04-12 15:53:16 -07001973 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001974 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001975
1976 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001977 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001978 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001979
1980 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001981 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001982 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001983 ranges = self.touched_src_ranges
1984 expected_sha1 = self.touched_src_sha1
1985 else:
1986 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1987 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001988
1989 # No blocks to be checked, skipping.
1990 if not ranges:
1991 return
1992
Tao Bao5ece99d2015-05-12 11:42:31 -07001993 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001994 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001995 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001996 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1997 '"%s.patch.dat")) then' % (
1998 self.device, ranges_str, expected_sha1,
1999 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07002000 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07002001 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00002002
Tianjie Xufc3422a2015-12-15 11:53:59 -08002003 if self.version >= 4:
2004
2005 # Bug: 21124327
2006 # When generating incrementals for the system and vendor partitions in
2007 # version 4 or newer, explicitly check the first block (which contains
2008 # the superblock) of the partition to see if it's what we expect. If
2009 # this check fails, give an explicit log message about the partition
2010 # having been remounted R/W (the most likely explanation).
2011 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002012 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002013
2014 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002015 if partition == "system":
2016 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2017 else:
2018 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002019 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002020 'ifelse (block_image_recover({device}, "{ranges}") && '
2021 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002022 'package_extract_file("{partition}.transfer.list"), '
2023 '"{partition}.new.dat", "{partition}.patch.dat"), '
2024 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002025 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002026 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002027 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002028
Tao Baodd2a5892015-03-12 12:32:37 -07002029 # Abort the OTA update. Note that the incremental OTA cannot be applied
2030 # even if it may match the checksum of the target partition.
2031 # a) If version < 3, operations like move and erase will make changes
2032 # unconditionally and damage the partition.
2033 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002034 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002035 if partition == "system":
2036 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2037 else:
2038 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2039 script.AppendExtra((
2040 'abort("E%d: %s partition has unexpected contents");\n'
2041 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002042
Yifan Hong10c530d2018-12-27 17:34:18 -08002043 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002044 partition = self.partition
2045 script.Print('Verifying the updated %s image...' % (partition,))
2046 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2047 ranges = self.tgt.care_map
2048 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002049 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002050 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002051 self.device, ranges_str,
2052 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002053
2054 # Bug: 20881595
2055 # Verify that extended blocks are really zeroed out.
2056 if self.tgt.extended:
2057 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002058 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002059 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002060 self.device, ranges_str,
2061 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002062 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002063 if partition == "system":
2064 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2065 else:
2066 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002067 script.AppendExtra(
2068 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002069 ' abort("E%d: %s partition has unexpected non-zero contents after '
2070 'OTA update");\n'
2071 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002072 else:
2073 script.Print('Verified the updated %s image.' % (partition,))
2074
Tianjie Xu209db462016-05-24 17:34:52 -07002075 if partition == "system":
2076 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2077 else:
2078 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2079
Tao Bao5fcaaef2015-06-01 13:40:49 -07002080 script.AppendExtra(
2081 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002082 ' abort("E%d: %s partition has unexpected contents after OTA '
2083 'update");\n'
2084 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002085
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002086 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002087 ZipWrite(output_zip,
2088 '{}.transfer.list'.format(self.path),
2089 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002090
Tao Bao76def242017-11-21 09:25:31 -08002091 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2092 # its size. Quailty 9 almost triples the compression time but doesn't
2093 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002094 # zip | brotli(quality 6) | brotli(quality 9)
2095 # compressed_size: 942M | 869M (~8% reduced) | 854M
2096 # compression_time: 75s | 265s | 719s
2097 # decompression_time: 15s | 25s | 25s
2098
2099 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002100 brotli_cmd = ['brotli', '--quality=6',
2101 '--output={}.new.dat.br'.format(self.path),
2102 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002103 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002104 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002105
2106 new_data_name = '{}.new.dat.br'.format(self.partition)
2107 ZipWrite(output_zip,
2108 '{}.new.dat.br'.format(self.path),
2109 new_data_name,
2110 compress_type=zipfile.ZIP_STORED)
2111 else:
2112 new_data_name = '{}.new.dat'.format(self.partition)
2113 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2114
Dan Albert8e0178d2015-01-27 15:53:15 -08002115 ZipWrite(output_zip,
2116 '{}.patch.dat'.format(self.path),
2117 '{}.patch.dat'.format(self.partition),
2118 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002119
Tianjie Xu209db462016-05-24 17:34:52 -07002120 if self.partition == "system":
2121 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2122 else:
2123 code = ErrorCode.VENDOR_UPDATE_FAILURE
2124
Yifan Hong10c530d2018-12-27 17:34:18 -08002125 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002126 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002127 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002128 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002129 device=self.device, partition=self.partition,
2130 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002131 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002132
Dan Albert8b72aef2015-03-23 19:13:21 -07002133 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002134 data = source.ReadRangeSet(ranges)
2135 ctx = sha1()
2136
2137 for p in data:
2138 ctx.update(p)
2139
2140 return ctx.hexdigest()
2141
Tao Baoe9b61912015-07-09 17:37:49 -07002142 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2143 """Return the hash value for all zero blocks."""
2144 zero_block = '\x00' * 4096
2145 ctx = sha1()
2146 for _ in range(num_blocks):
2147 ctx.update(zero_block)
2148
2149 return ctx.hexdigest()
2150
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002151
2152DataImage = blockimgdiff.DataImage
Yifan Hong8a66a712019-04-04 15:37:57 -07002153EmptyImage = blockimgdiff.EmptyImage
Tao Bao76def242017-11-21 09:25:31 -08002154
Doug Zongker96a57e72010-09-26 14:57:41 -07002155# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002156PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002157 "ext4": "EMMC",
2158 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002159 "f2fs": "EMMC",
2160 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002161}
Doug Zongker96a57e72010-09-26 14:57:41 -07002162
Tao Bao76def242017-11-21 09:25:31 -08002163
Doug Zongker96a57e72010-09-26 14:57:41 -07002164def GetTypeAndDevice(mount_point, info):
2165 fstab = info["fstab"]
2166 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002167 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2168 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002169 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002170 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002171
2172
2173def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002174 """Parses and converts a PEM-encoded certificate into DER-encoded.
2175
2176 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2177
2178 Returns:
2179 The decoded certificate string.
2180 """
2181 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002182 save = False
2183 for line in data.split("\n"):
2184 if "--END CERTIFICATE--" in line:
2185 break
2186 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002187 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002188 if "--BEGIN CERTIFICATE--" in line:
2189 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002190 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002191 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002192
Tao Bao04e1f012018-02-04 12:13:35 -08002193
2194def ExtractPublicKey(cert):
2195 """Extracts the public key (PEM-encoded) from the given certificate file.
2196
2197 Args:
2198 cert: The certificate filename.
2199
2200 Returns:
2201 The public key string.
2202
2203 Raises:
2204 AssertionError: On non-zero return from 'openssl'.
2205 """
2206 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2207 # While openssl 1.1 writes the key into the given filename followed by '-out',
2208 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2209 # stdout instead.
2210 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2211 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2212 pubkey, stderrdata = proc.communicate()
2213 assert proc.returncode == 0, \
2214 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2215 return pubkey
2216
2217
Tao Bao2cc0ca12019-03-15 10:44:43 -07002218def ExtractAvbPublicKey(key):
2219 """Extracts the AVB public key from the given public or private key.
2220
2221 Args:
2222 key: The input key file, which should be PEM-encoded public or private key.
2223
2224 Returns:
2225 The path to the extracted AVB public key file.
2226 """
2227 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2228 RunAndCheckOutput(
2229 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2230 return output
2231
2232
Doug Zongker412c02f2014-02-13 10:58:24 -08002233def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2234 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002235 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002236
Tao Bao6d5d6232018-03-09 17:04:42 -08002237 Most of the space in the boot and recovery images is just the kernel, which is
2238 identical for the two, so the resulting patch should be efficient. Add it to
2239 the output zip, along with a shell script that is run from init.rc on first
2240 boot to actually do the patching and install the new recovery image.
2241
2242 Args:
2243 input_dir: The top-level input directory of the target-files.zip.
2244 output_sink: The callback function that writes the result.
2245 recovery_img: File object for the recovery image.
2246 boot_img: File objects for the boot image.
2247 info_dict: A dict returned by common.LoadInfoDict() on the input
2248 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002249 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002250 if info_dict is None:
2251 info_dict = OPTIONS.info_dict
2252
Tao Bao6d5d6232018-03-09 17:04:42 -08002253 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002254
Tao Baof2cffbd2015-07-22 12:33:18 -07002255 if full_recovery_image:
2256 output_sink("etc/recovery.img", recovery_img.data)
2257
2258 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002259 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002260 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002261 # With system-root-image, boot and recovery images will have mismatching
2262 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2263 # to handle such a case.
2264 if system_root_image:
2265 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002266 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002267 assert not os.path.exists(path)
2268 else:
2269 diff_program = ["imgdiff"]
2270 if os.path.exists(path):
2271 diff_program.append("-b")
2272 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002273 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002274 else:
2275 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002276
2277 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2278 _, _, patch = d.ComputePatch()
2279 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002280
Dan Albertebb19aa2015-03-27 19:11:53 -07002281 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002282 # The following GetTypeAndDevice()s need to use the path in the target
2283 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002284 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2285 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2286 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002287 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002288
Tao Baof2cffbd2015-07-22 12:33:18 -07002289 if full_recovery_image:
2290 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002291if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2292 applypatch \\
2293 --flash /system/etc/recovery.img \\
2294 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2295 log -t recovery "Installing new recovery image: succeeded" || \\
2296 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002297else
2298 log -t recovery "Recovery image already installed"
2299fi
2300""" % {'type': recovery_type,
2301 'device': recovery_device,
2302 'sha1': recovery_img.sha1,
2303 'size': recovery_img.size}
2304 else:
2305 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002306if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2307 applypatch %(bonus_args)s \\
2308 --patch /system/recovery-from-boot.p \\
2309 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2310 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2311 log -t recovery "Installing new recovery image: succeeded" || \\
2312 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002313else
2314 log -t recovery "Recovery image already installed"
2315fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002316""" % {'boot_size': boot_img.size,
2317 'boot_sha1': boot_img.sha1,
2318 'recovery_size': recovery_img.size,
2319 'recovery_sha1': recovery_img.sha1,
2320 'boot_type': boot_type,
2321 'boot_device': boot_device,
2322 'recovery_type': recovery_type,
2323 'recovery_device': recovery_device,
2324 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002325
2326 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002327 # in the L release.
2328 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002329
Tao Bao32fcdab2018-10-12 10:30:39 -07002330 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002331
2332 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002333
2334
2335class DynamicPartitionUpdate(object):
2336 def __init__(self, src_group=None, tgt_group=None, progress=None,
2337 block_difference=None):
2338 self.src_group = src_group
2339 self.tgt_group = tgt_group
2340 self.progress = progress
2341 self.block_difference = block_difference
2342
2343 @property
2344 def src_size(self):
2345 if not self.block_difference:
2346 return 0
2347 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2348
2349 @property
2350 def tgt_size(self):
2351 if not self.block_difference:
2352 return 0
2353 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2354
2355 @staticmethod
2356 def _GetSparseImageSize(img):
2357 if not img:
2358 return 0
2359 return img.blocksize * img.total_blocks
2360
2361
2362class DynamicGroupUpdate(object):
2363 def __init__(self, src_size=None, tgt_size=None):
2364 # None: group does not exist. 0: no size limits.
2365 self.src_size = src_size
2366 self.tgt_size = tgt_size
2367
2368
2369class DynamicPartitionsDifference(object):
2370 def __init__(self, info_dict, block_diffs, progress_dict=None,
2371 source_info_dict=None):
2372 if progress_dict is None:
2373 progress_dict = dict()
2374
2375 self._remove_all_before_apply = False
2376 if source_info_dict is None:
2377 self._remove_all_before_apply = True
2378 source_info_dict = dict()
2379
2380 block_diff_dict = {e.partition:e for e in block_diffs}
2381 assert len(block_diff_dict) == len(block_diffs), \
2382 "Duplicated BlockDifference object for {}".format(
2383 [partition for partition, count in
2384 collections.Counter(e.partition for e in block_diffs).items()
2385 if count > 1])
2386
Yifan Hong79997e52019-01-23 16:56:19 -08002387 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002388
2389 for p, block_diff in block_diff_dict.items():
2390 self._partition_updates[p] = DynamicPartitionUpdate()
2391 self._partition_updates[p].block_difference = block_diff
2392
2393 for p, progress in progress_dict.items():
2394 if p in self._partition_updates:
2395 self._partition_updates[p].progress = progress
2396
2397 tgt_groups = shlex.split(info_dict.get(
2398 "super_partition_groups", "").strip())
2399 src_groups = shlex.split(source_info_dict.get(
2400 "super_partition_groups", "").strip())
2401
2402 for g in tgt_groups:
2403 for p in shlex.split(info_dict.get(
2404 "super_%s_partition_list" % g, "").strip()):
2405 assert p in self._partition_updates, \
2406 "{} is in target super_{}_partition_list but no BlockDifference " \
2407 "object is provided.".format(p, g)
2408 self._partition_updates[p].tgt_group = g
2409
2410 for g in src_groups:
2411 for p in shlex.split(source_info_dict.get(
2412 "super_%s_partition_list" % g, "").strip()):
2413 assert p in self._partition_updates, \
2414 "{} is in source super_{}_partition_list but no BlockDifference " \
2415 "object is provided.".format(p, g)
2416 self._partition_updates[p].src_group = g
2417
Yifan Hong45433e42019-01-18 13:55:25 -08002418 target_dynamic_partitions = set(shlex.split(info_dict.get(
2419 "dynamic_partition_list", "").strip()))
2420 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2421 if u.tgt_size)
2422 assert block_diffs_with_target == target_dynamic_partitions, \
2423 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2424 list(target_dynamic_partitions), list(block_diffs_with_target))
2425
2426 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2427 "dynamic_partition_list", "").strip()))
2428 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2429 if u.src_size)
2430 assert block_diffs_with_source == source_dynamic_partitions, \
2431 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2432 list(source_dynamic_partitions), list(block_diffs_with_source))
2433
Yifan Hong10c530d2018-12-27 17:34:18 -08002434 if self._partition_updates:
2435 logger.info("Updating dynamic partitions %s",
2436 self._partition_updates.keys())
2437
Yifan Hong79997e52019-01-23 16:56:19 -08002438 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002439
2440 for g in tgt_groups:
2441 self._group_updates[g] = DynamicGroupUpdate()
2442 self._group_updates[g].tgt_size = int(info_dict.get(
2443 "super_%s_group_size" % g, "0").strip())
2444
2445 for g in src_groups:
2446 if g not in self._group_updates:
2447 self._group_updates[g] = DynamicGroupUpdate()
2448 self._group_updates[g].src_size = int(source_info_dict.get(
2449 "super_%s_group_size" % g, "0").strip())
2450
2451 self._Compute()
2452
2453 def WriteScript(self, script, output_zip, write_verify_script=False):
2454 script.Comment('--- Start patching dynamic partitions ---')
2455 for p, u in self._partition_updates.items():
2456 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2457 script.Comment('Patch partition %s' % p)
2458 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2459 write_verify_script=False)
2460
2461 op_list_path = MakeTempFile()
2462 with open(op_list_path, 'w') as f:
2463 for line in self._op_list:
2464 f.write('{}\n'.format(line))
2465
2466 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2467
2468 script.Comment('Update dynamic partition metadata')
2469 script.AppendExtra('assert(update_dynamic_partitions('
2470 'package_extract_file("dynamic_partitions_op_list")));')
2471
2472 if write_verify_script:
2473 for p, u in self._partition_updates.items():
2474 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2475 u.block_difference.WritePostInstallVerifyScript(script)
2476 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2477
2478 for p, u in self._partition_updates.items():
2479 if u.tgt_size and u.src_size <= u.tgt_size:
2480 script.Comment('Patch partition %s' % p)
2481 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2482 write_verify_script=write_verify_script)
2483 if write_verify_script:
2484 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2485
2486 script.Comment('--- End patching dynamic partitions ---')
2487
2488 def _Compute(self):
2489 self._op_list = list()
2490
2491 def append(line):
2492 self._op_list.append(line)
2493
2494 def comment(line):
2495 self._op_list.append("# %s" % line)
2496
2497 if self._remove_all_before_apply:
2498 comment('Remove all existing dynamic partitions and groups before '
2499 'applying full OTA')
2500 append('remove_all_groups')
2501
2502 for p, u in self._partition_updates.items():
2503 if u.src_group and not u.tgt_group:
2504 append('remove %s' % p)
2505
2506 for p, u in self._partition_updates.items():
2507 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2508 comment('Move partition %s from %s to default' % (p, u.src_group))
2509 append('move %s default' % p)
2510
2511 for p, u in self._partition_updates.items():
2512 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2513 comment('Shrink partition %s from %d to %d' %
2514 (p, u.src_size, u.tgt_size))
2515 append('resize %s %s' % (p, u.tgt_size))
2516
2517 for g, u in self._group_updates.items():
2518 if u.src_size is not None and u.tgt_size is None:
2519 append('remove_group %s' % g)
2520 if (u.src_size is not None and u.tgt_size is not None and
2521 u.src_size > u.tgt_size):
2522 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2523 append('resize_group %s %d' % (g, u.tgt_size))
2524
2525 for g, u in self._group_updates.items():
2526 if u.src_size is None and u.tgt_size is not None:
2527 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2528 append('add_group %s %d' % (g, u.tgt_size))
2529 if (u.src_size is not None and u.tgt_size is not None and
2530 u.src_size < u.tgt_size):
2531 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2532 append('resize_group %s %d' % (g, u.tgt_size))
2533
2534 for p, u in self._partition_updates.items():
2535 if u.tgt_group and not u.src_group:
2536 comment('Add partition %s to group %s' % (p, u.tgt_group))
2537 append('add %s %s' % (p, u.tgt_group))
2538
2539 for p, u in self._partition_updates.items():
2540 if u.tgt_size and u.src_size < u.tgt_size:
2541 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2542 append('resize %s %d' % (p, u.tgt_size))
2543
2544 for p, u in self._partition_updates.items():
2545 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2546 comment('Move partition %s from default to %s' %
2547 (p, u.tgt_group))
2548 append('move %s %s' % (p, u.tgt_group))