blob: 1b2505aaac607bce9f75a9453dc0d401799c8dea [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
Tianjie Xu861f4132018-09-12 11:49:33 -0700102# Partitions that should have their care_map added to META/care_map.pb
103PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
104 'odm')
105
106
Tianjie Xu209db462016-05-24 17:34:52 -0700107class ErrorCode(object):
108 """Define error_codes for failures that happen during the actual
109 update package installation.
110
111 Error codes 0-999 are reserved for failures before the package
112 installation (i.e. low battery, package verification failure).
113 Detailed code in 'bootable/recovery/error_code.h' """
114
115 SYSTEM_VERIFICATION_FAILURE = 1000
116 SYSTEM_UPDATE_FAILURE = 1001
117 SYSTEM_UNEXPECTED_CONTENTS = 1002
118 SYSTEM_NONZERO_CONTENTS = 1003
119 SYSTEM_RECOVER_FAILURE = 1004
120 VENDOR_VERIFICATION_FAILURE = 2000
121 VENDOR_UPDATE_FAILURE = 2001
122 VENDOR_UNEXPECTED_CONTENTS = 2002
123 VENDOR_NONZERO_CONTENTS = 2003
124 VENDOR_RECOVER_FAILURE = 2004
125 OEM_PROP_MISMATCH = 3000
126 FINGERPRINT_MISMATCH = 3001
127 THUMBPRINT_MISMATCH = 3002
128 OLDER_BUILD = 3003
129 DEVICE_MISMATCH = 3004
130 BAD_PATCH_FILE = 3005
131 INSUFFICIENT_CACHE_SPACE = 3006
132 TUNE_PARTITION_FAILURE = 3007
133 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800134
Tao Bao80921982018-03-21 21:02:19 -0700135
Dan Albert8b72aef2015-03-23 19:13:21 -0700136class ExternalError(RuntimeError):
137 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700138
139
Tao Bao32fcdab2018-10-12 10:30:39 -0700140def InitLogging():
141 DEFAULT_LOGGING_CONFIG = {
142 'version': 1,
143 'disable_existing_loggers': False,
144 'formatters': {
145 'standard': {
146 'format':
147 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
148 'datefmt': '%Y-%m-%d %H:%M:%S',
149 },
150 },
151 'handlers': {
152 'default': {
153 'class': 'logging.StreamHandler',
154 'formatter': 'standard',
155 },
156 },
157 'loggers': {
158 '': {
159 'handlers': ['default'],
160 'level': 'WARNING',
161 'propagate': True,
162 }
163 }
164 }
165 env_config = os.getenv('LOGGING_CONFIG')
166 if env_config:
167 with open(env_config) as f:
168 config = json.load(f)
169 else:
170 config = DEFAULT_LOGGING_CONFIG
171
172 # Increase the logging level for verbose mode.
173 if OPTIONS.verbose:
174 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
175 config['loggers']['']['level'] = 'INFO'
176
177 logging.config.dictConfig(config)
178
179
Tao Bao39451582017-05-04 11:10:47 -0700180def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700181 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700182
Tao Bao73dd4f42018-10-04 16:25:33 -0700183 Args:
184 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700185 verbose: Whether the commands should be shown. Default to the global
186 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700187 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
188 stdin, etc. stdout and stderr will default to subprocess.PIPE and
189 subprocess.STDOUT respectively unless caller specifies any of them.
190
191 Returns:
192 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700193 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700194 if 'stdout' not in kwargs and 'stderr' not in kwargs:
195 kwargs['stdout'] = subprocess.PIPE
196 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700197 # Don't log any if caller explicitly says so.
198 if verbose != False:
199 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700200 return subprocess.Popen(args, **kwargs)
201
202
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800203def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800204 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800205
206 Args:
207 args: The command represented as a list of strings.
208 verbose: Whether the commands should be shown. Default to the global
209 verbosity if unspecified.
210 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
211 stdin, etc. stdout and stderr will default to subprocess.PIPE and
212 subprocess.STDOUT respectively unless caller specifies any of them.
213
Bill Peckham889b0c62019-02-21 18:53:37 -0800214 Raises:
215 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800216 """
217 proc = Run(args, verbose=verbose, **kwargs)
218 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800219
220 if proc.returncode != 0:
221 raise ExternalError(
222 "Failed to run command '{}' (exit code {})".format(
223 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800224
225
Tao Bao986ee862018-10-04 15:46:16 -0700226def RunAndCheckOutput(args, verbose=None, **kwargs):
227 """Runs the given command and returns the output.
228
229 Args:
230 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700231 verbose: Whether the commands should be shown. Default to the global
232 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700233 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
234 stdin, etc. stdout and stderr will default to subprocess.PIPE and
235 subprocess.STDOUT respectively unless caller specifies any of them.
236
237 Returns:
238 The output string.
239
240 Raises:
241 ExternalError: On non-zero exit from the command.
242 """
Tao Bao986ee862018-10-04 15:46:16 -0700243 proc = Run(args, verbose=verbose, **kwargs)
244 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700245 # Don't log any if caller explicitly says so.
246 if verbose != False:
247 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700248 if proc.returncode != 0:
249 raise ExternalError(
250 "Failed to run command '{}' (exit code {}):\n{}".format(
251 args, proc.returncode, output))
252 return output
253
254
Tao Baoc765cca2018-01-31 17:32:40 -0800255def RoundUpTo4K(value):
256 rounded_up = value + 4095
257 return rounded_up - (rounded_up % 4096)
258
259
Ying Wang7e6d4e42010-12-13 16:25:36 -0800260def CloseInheritedPipes():
261 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
262 before doing other work."""
263 if platform.system() != "Darwin":
264 return
265 for d in range(3, 1025):
266 try:
267 stat = os.fstat(d)
268 if stat is not None:
269 pipebit = stat[0] & 0x1000
270 if pipebit != 0:
271 os.close(d)
272 except OSError:
273 pass
274
275
Tao Bao410ad8b2018-08-24 12:08:38 -0700276def LoadInfoDict(input_file, repacking=False):
277 """Loads the key/value pairs from the given input target_files.
278
279 It reads `META/misc_info.txt` file in the target_files input, does sanity
280 checks and returns the parsed key/value pairs for to the given build. It's
281 usually called early when working on input target_files files, e.g. when
282 generating OTAs, or signing builds. Note that the function may be called
283 against an old target_files file (i.e. from past dessert releases). So the
284 property parsing needs to be backward compatible.
285
286 In a `META/misc_info.txt`, a few properties are stored as links to the files
287 in the PRODUCT_OUT directory. It works fine with the build system. However,
288 they are no longer available when (re)generating images from target_files zip.
289 When `repacking` is True, redirect these properties to the actual files in the
290 unzipped directory.
291
292 Args:
293 input_file: The input target_files file, which could be an open
294 zipfile.ZipFile instance, or a str for the dir that contains the files
295 unzipped from a target_files file.
296 repacking: Whether it's trying repack an target_files file after loading the
297 info dict (default: False). If so, it will rewrite a few loaded
298 properties (e.g. selinux_fc, root_dir) to point to the actual files in
299 target_files file. When doing repacking, `input_file` must be a dir.
300
301 Returns:
302 A dict that contains the parsed key/value pairs.
303
304 Raises:
305 AssertionError: On invalid input arguments.
306 ValueError: On malformed input values.
307 """
308 if repacking:
309 assert isinstance(input_file, str), \
310 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700311
Doug Zongkerc9253822014-02-04 12:17:58 -0800312 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700313 if isinstance(input_file, zipfile.ZipFile):
314 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800315 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700316 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800317 try:
318 with open(path) as f:
319 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700320 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800321 if e.errno == errno.ENOENT:
322 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800323
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700324 try:
Michael Runge6e836112014-04-15 17:40:21 -0700325 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700326 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700327 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700328
Tao Bao410ad8b2018-08-24 12:08:38 -0700329 if "recovery_api_version" not in d:
330 raise ValueError("Failed to find 'recovery_api_version'")
331 if "fstab_version" not in d:
332 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800333
Tao Bao410ad8b2018-08-24 12:08:38 -0700334 if repacking:
Tao Bao13ebc022019-05-08 12:37:08 -0700335 # "selinux_fc" should point to the file_contexts file (file_contexts.bin)
336 # under META/.
Tao Bao79735a62015-08-28 10:52:03 -0700337 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700338 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700339 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700340
Tom Cherryd14b8952018-08-09 14:26:00 -0700341 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700342
Tom Cherryd14b8952018-08-09 14:26:00 -0700343 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700344 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700345 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700346 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700347
Tao Baof54216f2016-03-29 15:12:37 -0700348 # Redirect {system,vendor}_base_fs_file.
349 if "system_base_fs_file" in d:
350 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700351 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700352 if os.path.exists(system_base_fs_file):
353 d["system_base_fs_file"] = system_base_fs_file
354 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700355 logger.warning(
356 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700357 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700358
359 if "vendor_base_fs_file" in d:
360 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700361 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700362 if os.path.exists(vendor_base_fs_file):
363 d["vendor_base_fs_file"] = vendor_base_fs_file
364 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700365 logger.warning(
366 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700367 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700368
Doug Zongker37974732010-09-16 17:44:38 -0700369 def makeint(key):
370 if key in d:
371 d[key] = int(d[key], 0)
372
373 makeint("recovery_api_version")
374 makeint("blocksize")
375 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700376 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700377 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700378 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700379 makeint("recovery_size")
380 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800381 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700382
Tao Baoa57ab9f2018-08-24 12:08:38 -0700383 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
384 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
385 # cases, since it may load the info_dict from an old build (e.g. when
386 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800387 system_root_image = d.get("system_root_image") == "true"
388 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700389 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700390 if isinstance(input_file, zipfile.ZipFile):
391 if recovery_fstab_path not in input_file.namelist():
392 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
393 else:
394 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
395 if not os.path.exists(path):
396 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800397 d["fstab"] = LoadRecoveryFSTab(
398 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700399
Tao Bao76def242017-11-21 09:25:31 -0800400 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700401 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700402 if isinstance(input_file, zipfile.ZipFile):
403 if recovery_fstab_path not in input_file.namelist():
404 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
405 else:
406 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
407 if not os.path.exists(path):
408 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800409 d["fstab"] = LoadRecoveryFSTab(
410 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700411
Tianjie Xucfa86222016-03-07 16:31:19 -0800412 else:
413 d["fstab"] = None
414
Tianjie Xu861f4132018-09-12 11:49:33 -0700415 # Tries to load the build props for all partitions with care_map, including
416 # system and vendor.
417 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800418 partition_prop = "{}.build.prop".format(partition)
419 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700420 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800421 # Some partition might use /<partition>/etc/build.prop as the new path.
422 # TODO: try new path first when majority of them switch to the new path.
423 if not d[partition_prop]:
424 d[partition_prop] = LoadBuildProp(
425 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700426 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800427
428 # Set up the salt (based on fingerprint or thumbprint) that will be used when
429 # adding AVB footer.
430 if d.get("avb_enable") == "true":
431 fp = None
432 if "build.prop" in d:
433 build_prop = d["build.prop"]
434 if "ro.build.fingerprint" in build_prop:
435 fp = build_prop["ro.build.fingerprint"]
436 elif "ro.build.thumbprint" in build_prop:
437 fp = build_prop["ro.build.thumbprint"]
438 if fp:
439 d["avb_salt"] = sha256(fp).hexdigest()
440
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700441 return d
442
Tao Baod1de6f32017-03-01 16:38:48 -0800443
Tao Baobcd1d162017-08-26 13:10:26 -0700444def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700445 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700446 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700447 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700448 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700449 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700450 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700451
Tao Baod1de6f32017-03-01 16:38:48 -0800452
Michael Runge6e836112014-04-15 17:40:21 -0700453def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700454 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700455 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700456 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700457 if not line or line.startswith("#"):
458 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700459 if "=" in line:
460 name, value = line.split("=", 1)
461 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700462 return d
463
Tao Baod1de6f32017-03-01 16:38:48 -0800464
Tianjie Xucfa86222016-03-07 16:31:19 -0800465def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
466 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700467 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800468 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700469 self.mount_point = mount_point
470 self.fs_type = fs_type
471 self.device = device
472 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700473 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700474
475 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800476 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700477 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700478 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700479 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700480
Tao Baod1de6f32017-03-01 16:38:48 -0800481 assert fstab_version == 2
482
483 d = {}
484 for line in data.split("\n"):
485 line = line.strip()
486 if not line or line.startswith("#"):
487 continue
488
489 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
490 pieces = line.split()
491 if len(pieces) != 5:
492 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
493
494 # Ignore entries that are managed by vold.
495 options = pieces[4]
496 if "voldmanaged=" in options:
497 continue
498
499 # It's a good line, parse it.
500 length = 0
501 options = options.split(",")
502 for i in options:
503 if i.startswith("length="):
504 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800505 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800506 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700507 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800508
Tao Baod1de6f32017-03-01 16:38:48 -0800509 mount_flags = pieces[3]
510 # Honor the SELinux context if present.
511 context = None
512 for i in mount_flags.split(","):
513 if i.startswith("context="):
514 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800515
Tao Baod1de6f32017-03-01 16:38:48 -0800516 mount_point = pieces[1]
517 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
518 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800519
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700520 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700521 # system. Other areas assume system is always at "/system" so point /system
522 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700523 if system_root_image:
524 assert not d.has_key("/system") and d.has_key("/")
525 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700526 return d
527
528
Doug Zongker37974732010-09-16 17:44:38 -0700529def DumpInfoDict(d):
530 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700531 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700532
Dan Albert8b72aef2015-03-23 19:13:21 -0700533
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800534def AppendAVBSigningArgs(cmd, partition):
535 """Append signing arguments for avbtool."""
536 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
537 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
538 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
539 if key_path and algorithm:
540 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700541 avb_salt = OPTIONS.info_dict.get("avb_salt")
542 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700543 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700544 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800545
546
Tao Bao02a08592018-07-22 12:40:45 -0700547def GetAvbChainedPartitionArg(partition, info_dict, key=None):
548 """Constructs and returns the arg to build or verify a chained partition.
549
550 Args:
551 partition: The partition name.
552 info_dict: The info dict to look up the key info and rollback index
553 location.
554 key: The key to be used for building or verifying the partition. Defaults to
555 the key listed in info_dict.
556
557 Returns:
558 A string of form "partition:rollback_index_location:key" that can be used to
559 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700560 """
561 if key is None:
562 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao2cc0ca12019-03-15 10:44:43 -0700563 pubkey_path = ExtractAvbPublicKey(key)
Tao Bao02a08592018-07-22 12:40:45 -0700564 rollback_index_location = info_dict[
565 "avb_" + partition + "_rollback_index_location"]
566 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
567
568
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700569def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800570 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700571 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700572
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700573 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800574 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
575 we are building a two-step special image (i.e. building a recovery image to
576 be loaded into /boot in two-step OTAs).
577
578 Return the image data, or None if sourcedir does not appear to contains files
579 for building the requested image.
580 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700581
582 def make_ramdisk():
583 ramdisk_img = tempfile.NamedTemporaryFile()
584
585 if os.access(fs_config_file, os.F_OK):
586 cmd = ["mkbootfs", "-f", fs_config_file,
587 os.path.join(sourcedir, "RAMDISK")]
588 else:
589 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
590 p1 = Run(cmd, stdout=subprocess.PIPE)
591 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
592
593 p2.wait()
594 p1.wait()
595 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
596 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
597
598 return ramdisk_img
599
600 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
601 return None
602
603 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700604 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700605
Doug Zongkerd5131602012-08-02 14:46:42 -0700606 if info_dict is None:
607 info_dict = OPTIONS.info_dict
608
Doug Zongkereef39442009-04-02 12:14:19 -0700609 img = tempfile.NamedTemporaryFile()
610
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700611 if has_ramdisk:
612 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700613
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800614 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
615 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
616
617 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700618
Benoit Fradina45a8682014-07-14 21:00:43 +0200619 fn = os.path.join(sourcedir, "second")
620 if os.access(fn, os.F_OK):
621 cmd.append("--second")
622 cmd.append(fn)
623
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800624 fn = os.path.join(sourcedir, "dtb")
625 if os.access(fn, os.F_OK):
626 cmd.append("--dtb")
627 cmd.append(fn)
628
Doug Zongker171f1cd2009-06-15 22:36:37 -0700629 fn = os.path.join(sourcedir, "cmdline")
630 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700631 cmd.append("--cmdline")
632 cmd.append(open(fn).read().rstrip("\n"))
633
634 fn = os.path.join(sourcedir, "base")
635 if os.access(fn, os.F_OK):
636 cmd.append("--base")
637 cmd.append(open(fn).read().rstrip("\n"))
638
Ying Wang4de6b5b2010-08-25 14:29:34 -0700639 fn = os.path.join(sourcedir, "pagesize")
640 if os.access(fn, os.F_OK):
641 cmd.append("--pagesize")
642 cmd.append(open(fn).read().rstrip("\n"))
643
Tao Bao76def242017-11-21 09:25:31 -0800644 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700645 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700646 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700647
Tao Bao76def242017-11-21 09:25:31 -0800648 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000649 if args and args.strip():
650 cmd.extend(shlex.split(args))
651
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700652 if has_ramdisk:
653 cmd.extend(["--ramdisk", ramdisk_img.name])
654
Tao Baod95e9fd2015-03-29 23:07:41 -0700655 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800656 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700657 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700658 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700659 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700660 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700661
Tao Baobf70c312017-07-11 17:27:55 -0700662 # "boot" or "recovery", without extension.
663 partition_name = os.path.basename(sourcedir).lower()
664
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800665 if partition_name == "recovery":
666 if info_dict.get("include_recovery_dtbo") == "true":
667 fn = os.path.join(sourcedir, "recovery_dtbo")
668 cmd.extend(["--recovery_dtbo", fn])
669 if info_dict.get("include_recovery_acpio") == "true":
670 fn = os.path.join(sourcedir, "recovery_acpio")
671 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700672
Tao Bao986ee862018-10-04 15:46:16 -0700673 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700674
Tao Bao76def242017-11-21 09:25:31 -0800675 if (info_dict.get("boot_signer") == "true" and
676 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800677 # Hard-code the path as "/boot" for two-step special recovery image (which
678 # will be loaded into /boot during the two-step OTA).
679 if two_step_image:
680 path = "/boot"
681 else:
Tao Baobf70c312017-07-11 17:27:55 -0700682 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700683 cmd = [OPTIONS.boot_signer_path]
684 cmd.extend(OPTIONS.boot_signer_args)
685 cmd.extend([path, img.name,
686 info_dict["verity_key"] + ".pk8",
687 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700688 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700689
Tao Baod95e9fd2015-03-29 23:07:41 -0700690 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800691 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700692 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700693 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800694 # We have switched from the prebuilt futility binary to using the tool
695 # (futility-host) built from the source. Override the setting in the old
696 # TF.zip.
697 futility = info_dict["futility"]
698 if futility.startswith("prebuilts/"):
699 futility = "futility-host"
700 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700701 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700702 info_dict["vboot_key"] + ".vbprivk",
703 info_dict["vboot_subkey"] + ".vbprivk",
704 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700705 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700706 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700707
Tao Baof3282b42015-04-01 11:21:55 -0700708 # Clean up the temp files.
709 img_unsigned.close()
710 img_keyblock.close()
711
David Zeuthen8fecb282017-12-01 16:24:01 -0500712 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800713 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700714 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500715 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400716 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700717 "--partition_size", str(part_size), "--partition_name",
718 partition_name]
719 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500720 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400721 if args and args.strip():
722 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700723 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500724
725 img.seek(os.SEEK_SET, 0)
726 data = img.read()
727
728 if has_ramdisk:
729 ramdisk_img.close()
730 img.close()
731
732 return data
733
734
Doug Zongkerd5131602012-08-02 14:46:42 -0700735def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800736 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700737 """Return a File object with the desired bootable image.
738
739 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
740 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
741 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700742
Doug Zongker55d93282011-01-25 17:03:34 -0800743 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
744 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700745 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800746 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700747
748 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
749 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700750 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700751 return File.FromLocalFile(name, prebuilt_path)
752
Tao Bao32fcdab2018-10-12 10:30:39 -0700753 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700754
755 if info_dict is None:
756 info_dict = OPTIONS.info_dict
757
758 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800759 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
760 # for recovery.
761 has_ramdisk = (info_dict.get("system_root_image") != "true" or
762 prebuilt_name != "boot.img" or
763 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700764
Doug Zongker6f1d0312014-08-22 08:07:12 -0700765 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400766 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
767 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800768 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700769 if data:
770 return File(name, data)
771 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800772
Doug Zongkereef39442009-04-02 12:14:19 -0700773
Narayan Kamatha07bf042017-08-14 14:49:21 +0100774def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800775 """Gunzips the given gzip compressed file to a given output file."""
776 with gzip.open(in_filename, "rb") as in_file, \
777 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100778 shutil.copyfileobj(in_file, out_file)
779
780
Tao Bao0ff15de2019-03-20 11:26:06 -0700781def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800782 """Unzips the archive to the given directory.
783
784 Args:
785 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800786 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700787 patterns: Files to unzip from the archive. If omitted, will unzip the entire
788 archvie. Non-matching patterns will be filtered out. If there's no match
789 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800790 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800791 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700792 if patterns is not None:
793 # Filter out non-matching patterns. unzip will complain otherwise.
794 with zipfile.ZipFile(filename) as input_zip:
795 names = input_zip.namelist()
796 filtered = [
797 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
798
799 # There isn't any matching files. Don't unzip anything.
800 if not filtered:
801 return
802 cmd.extend(filtered)
803
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800804 RunAndCheckOutput(cmd)
805
806
Doug Zongker75f17362009-12-08 13:46:44 -0800807def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800808 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800809
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800810 Args:
811 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
812 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
813
814 pattern: Files to unzip from the archive. If omitted, will unzip the entire
815 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800816
Tao Bao1c830bf2017-12-25 10:43:47 -0800817 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800818 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800819 """
Doug Zongkereef39442009-04-02 12:14:19 -0700820
Tao Bao1c830bf2017-12-25 10:43:47 -0800821 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800822 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
823 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800824 UnzipToDir(m.group(1), tmp, pattern)
825 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800826 filename = m.group(1)
827 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800828 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800829
Tao Baodba59ee2018-01-09 13:21:02 -0800830 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700831
832
Yifan Hong8a66a712019-04-04 15:37:57 -0700833def GetUserImage(which, tmpdir, input_zip,
834 info_dict=None,
835 allow_shared_blocks=None,
836 hashtree_info_generator=None,
837 reset_file_map=False):
838 """Returns an Image object suitable for passing to BlockImageDiff.
839
840 This function loads the specified image from the given path. If the specified
841 image is sparse, it also performs additional processing for OTA purpose. For
842 example, it always adds block 0 to clobbered blocks list. It also detects
843 files that cannot be reconstructed from the block list, for whom we should
844 avoid applying imgdiff.
845
846 Args:
847 which: The partition name.
848 tmpdir: The directory that contains the prebuilt image and block map file.
849 input_zip: The target-files ZIP archive.
850 info_dict: The dict to be looked up for relevant info.
851 allow_shared_blocks: If image is sparse, whether having shared blocks is
852 allowed. If none, it is looked up from info_dict.
853 hashtree_info_generator: If present and image is sparse, generates the
854 hashtree_info for this sparse image.
855 reset_file_map: If true and image is sparse, reset file map before returning
856 the image.
857 Returns:
858 A Image object. If it is a sparse image and reset_file_map is False, the
859 image will have file_map info loaded.
860 """
861 if info_dict == None:
862 info_dict = LoadInfoDict(input_zip)
863
864 is_sparse = info_dict.get("extfs_sparse_flag")
865
866 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
867 # shared blocks (i.e. some blocks will show up in multiple files' block
868 # list). We can only allocate such shared blocks to the first "owner", and
869 # disable imgdiff for all later occurrences.
870 if allow_shared_blocks is None:
871 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
872
873 if is_sparse:
874 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
875 hashtree_info_generator)
876 if reset_file_map:
877 img.ResetFileMap()
878 return img
879 else:
880 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
881
882
883def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
884 """Returns a Image object suitable for passing to BlockImageDiff.
885
886 This function loads the specified non-sparse image from the given path.
887
888 Args:
889 which: The partition name.
890 tmpdir: The directory that contains the prebuilt image and block map file.
891 Returns:
892 A Image object.
893 """
894 path = os.path.join(tmpdir, "IMAGES", which + ".img")
895 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
896
897 # The image and map files must have been created prior to calling
898 # ota_from_target_files.py (since LMP).
899 assert os.path.exists(path) and os.path.exists(mappath)
900
901 return blockimgdiff.FileImage(path, hashtree_info_generator=
902 hashtree_info_generator)
903
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700904def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
905 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800906 """Returns a SparseImage object suitable for passing to BlockImageDiff.
907
908 This function loads the specified sparse image from the given path, and
909 performs additional processing for OTA purpose. For example, it always adds
910 block 0 to clobbered blocks list. It also detects files that cannot be
911 reconstructed from the block list, for whom we should avoid applying imgdiff.
912
913 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700914 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800915 tmpdir: The directory that contains the prebuilt image and block map file.
916 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800917 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700918 hashtree_info_generator: If present, generates the hashtree_info for this
919 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800920 Returns:
921 A SparseImage object, with file_map info loaded.
922 """
Tao Baoc765cca2018-01-31 17:32:40 -0800923 path = os.path.join(tmpdir, "IMAGES", which + ".img")
924 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
925
926 # The image and map files must have been created prior to calling
927 # ota_from_target_files.py (since LMP).
928 assert os.path.exists(path) and os.path.exists(mappath)
929
930 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
931 # it to clobbered_blocks so that it will be written to the target
932 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
933 clobbered_blocks = "0"
934
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700935 image = sparse_img.SparseImage(
936 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
937 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800938
939 # block.map may contain less blocks, because mke2fs may skip allocating blocks
940 # if they contain all zeros. We can't reconstruct such a file from its block
941 # list. Tag such entries accordingly. (Bug: 65213616)
942 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800943 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700944 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800945 continue
946
Tom Cherryd14b8952018-08-09 14:26:00 -0700947 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
948 # filename listed in system.map may contain an additional leading slash
949 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
950 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700951 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
952
Tom Cherryd14b8952018-08-09 14:26:00 -0700953 # Special handling another case, where files not under /system
954 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700955 if which == 'system' and not arcname.startswith('SYSTEM'):
956 arcname = 'ROOT/' + arcname
957
958 assert arcname in input_zip.namelist(), \
959 "Failed to find the ZIP entry for {}".format(entry)
960
Tao Baoc765cca2018-01-31 17:32:40 -0800961 info = input_zip.getinfo(arcname)
962 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800963
964 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800965 # image, check the original block list to determine its completeness. Note
966 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800967 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800968 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800969
Tao Baoc765cca2018-01-31 17:32:40 -0800970 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
971 ranges.extra['incomplete'] = True
972
973 return image
974
975
Doug Zongkereef39442009-04-02 12:14:19 -0700976def GetKeyPasswords(keylist):
977 """Given a list of keys, prompt the user to enter passwords for
978 those which require them. Return a {key: password} dict. password
979 will be None if the key has no password."""
980
Doug Zongker8ce7c252009-05-22 13:34:54 -0700981 no_passwords = []
982 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700983 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700984 devnull = open("/dev/null", "w+b")
985 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800986 # We don't need a password for things that aren't really keys.
987 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700988 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700989 continue
990
T.R. Fullhart37e10522013-03-18 10:31:26 -0700991 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700992 "-inform", "DER", "-nocrypt"],
993 stdin=devnull.fileno(),
994 stdout=devnull.fileno(),
995 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700996 p.communicate()
997 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700998 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700999 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001000 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001001 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1002 "-inform", "DER", "-passin", "pass:"],
1003 stdin=devnull.fileno(),
1004 stdout=devnull.fileno(),
1005 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001006 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001007 if p.returncode == 0:
1008 # Encrypted key with empty string as password.
1009 key_passwords[k] = ''
1010 elif stderr.startswith('Error decrypting key'):
1011 # Definitely encrypted key.
1012 # It would have said "Error reading key" if it didn't parse correctly.
1013 need_passwords.append(k)
1014 else:
1015 # Potentially, a type of key that openssl doesn't understand.
1016 # We'll let the routines in signapk.jar handle it.
1017 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001018 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001019
T.R. Fullhart37e10522013-03-18 10:31:26 -07001020 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001021 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001022 return key_passwords
1023
1024
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001025def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001026 """Gets the minSdkVersion declared in the APK.
1027
1028 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1029 This can be both a decimal number (API Level) or a codename.
1030
1031 Args:
1032 apk_name: The APK filename.
1033
1034 Returns:
1035 The parsed SDK version string.
1036
1037 Raises:
1038 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001039 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001040 proc = Run(
1041 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1042 stderr=subprocess.PIPE)
1043 stdoutdata, stderrdata = proc.communicate()
1044 if proc.returncode != 0:
1045 raise ExternalError(
1046 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1047 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001048
Tao Baof47bf0f2018-03-21 23:28:51 -07001049 for line in stdoutdata.split("\n"):
1050 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001051 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1052 if m:
1053 return m.group(1)
1054 raise ExternalError("No minSdkVersion returned by aapt")
1055
1056
1057def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001058 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001059
Tao Baof47bf0f2018-03-21 23:28:51 -07001060 If minSdkVersion is set to a codename, it is translated to a number using the
1061 provided map.
1062
1063 Args:
1064 apk_name: The APK filename.
1065
1066 Returns:
1067 The parsed SDK version number.
1068
1069 Raises:
1070 ExternalError: On failing to get the min SDK version number.
1071 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001072 version = GetMinSdkVersion(apk_name)
1073 try:
1074 return int(version)
1075 except ValueError:
1076 # Not a decimal number. Codename?
1077 if version in codename_to_api_level_map:
1078 return codename_to_api_level_map[version]
1079 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001080 raise ExternalError(
1081 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1082 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001083
1084
1085def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001086 codename_to_api_level_map=None, whole_file=False,
1087 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001088 """Sign the input_name zip/jar/apk, producing output_name. Use the
1089 given key and password (the latter may be None if the key does not
1090 have a password.
1091
Doug Zongker951495f2009-08-14 12:44:19 -07001092 If whole_file is true, use the "-w" option to SignApk to embed a
1093 signature that covers the whole file in the archive comment of the
1094 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001095
1096 min_api_level is the API Level (int) of the oldest platform this file may end
1097 up on. If not specified for an APK, the API Level is obtained by interpreting
1098 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1099
1100 codename_to_api_level_map is needed to translate the codename which may be
1101 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001102
1103 Caller may optionally specify extra args to be passed to SignApk, which
1104 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001105 """
Tao Bao76def242017-11-21 09:25:31 -08001106 if codename_to_api_level_map is None:
1107 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001108 if extra_signapk_args is None:
1109 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001110
Alex Klyubin9667b182015-12-10 13:38:50 -08001111 java_library_path = os.path.join(
1112 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1113
Tao Baoe95540e2016-11-08 12:08:53 -08001114 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1115 ["-Djava.library.path=" + java_library_path,
1116 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001117 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001118 if whole_file:
1119 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001120
1121 min_sdk_version = min_api_level
1122 if min_sdk_version is None:
1123 if not whole_file:
1124 min_sdk_version = GetMinSdkVersionInt(
1125 input_name, codename_to_api_level_map)
1126 if min_sdk_version is not None:
1127 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1128
T.R. Fullhart37e10522013-03-18 10:31:26 -07001129 cmd.extend([key + OPTIONS.public_key_suffix,
1130 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001131 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001132
Tao Bao73dd4f42018-10-04 16:25:33 -07001133 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001134 if password is not None:
1135 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001136 stdoutdata, _ = proc.communicate(password)
1137 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001138 raise ExternalError(
1139 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001140 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001141
Doug Zongkereef39442009-04-02 12:14:19 -07001142
Doug Zongker37974732010-09-16 17:44:38 -07001143def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001144 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001145
Tao Bao9dd909e2017-11-14 11:27:32 -08001146 For non-AVB images, raise exception if the data is too big. Print a warning
1147 if the data is nearing the maximum size.
1148
1149 For AVB images, the actual image size should be identical to the limit.
1150
1151 Args:
1152 data: A string that contains all the data for the partition.
1153 target: The partition name. The ".img" suffix is optional.
1154 info_dict: The dict to be looked up for relevant info.
1155 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001156 if target.endswith(".img"):
1157 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001158 mount_point = "/" + target
1159
Ying Wangf8824af2014-06-03 14:07:27 -07001160 fs_type = None
1161 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001162 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001163 if mount_point == "/userdata":
1164 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001165 p = info_dict["fstab"][mount_point]
1166 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001167 device = p.device
1168 if "/" in device:
1169 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001170 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001171 if not fs_type or not limit:
1172 return
Doug Zongkereef39442009-04-02 12:14:19 -07001173
Andrew Boie0f9aec82012-02-14 09:32:52 -08001174 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001175 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1176 # path.
1177 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1178 if size != limit:
1179 raise ExternalError(
1180 "Mismatching image size for %s: expected %d actual %d" % (
1181 target, limit, size))
1182 else:
1183 pct = float(size) * 100.0 / limit
1184 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1185 if pct >= 99.0:
1186 raise ExternalError(msg)
1187 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001188 logger.warning("\n WARNING: %s\n", msg)
1189 else:
1190 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001191
1192
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001193def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001194 """Parses the APK certs info from a given target-files zip.
1195
1196 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1197 tuple with the following elements: (1) a dictionary that maps packages to
1198 certs (based on the "certificate" and "private_key" attributes in the file;
1199 (2) a string representing the extension of compressed APKs in the target files
1200 (e.g ".gz", ".bro").
1201
1202 Args:
1203 tf_zip: The input target_files ZipFile (already open).
1204
1205 Returns:
1206 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1207 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1208 no compressed APKs.
1209 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001210 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001211 compressed_extension = None
1212
Tao Bao0f990332017-09-08 19:02:54 -07001213 # META/apkcerts.txt contains the info for _all_ the packages known at build
1214 # time. Filter out the ones that are not installed.
1215 installed_files = set()
1216 for name in tf_zip.namelist():
1217 basename = os.path.basename(name)
1218 if basename:
1219 installed_files.add(basename)
1220
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001221 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1222 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001223 if not line:
1224 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001225 m = re.match(
1226 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1227 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1228 line)
1229 if not m:
1230 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001231
Tao Bao818ddf52018-01-05 11:17:34 -08001232 matches = m.groupdict()
1233 cert = matches["CERT"]
1234 privkey = matches["PRIVKEY"]
1235 name = matches["NAME"]
1236 this_compressed_extension = matches["COMPRESSED"]
1237
1238 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1239 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1240 if cert in SPECIAL_CERT_STRINGS and not privkey:
1241 certmap[name] = cert
1242 elif (cert.endswith(OPTIONS.public_key_suffix) and
1243 privkey.endswith(OPTIONS.private_key_suffix) and
1244 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1245 certmap[name] = cert[:-public_key_suffix_len]
1246 else:
1247 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1248
1249 if not this_compressed_extension:
1250 continue
1251
1252 # Only count the installed files.
1253 filename = name + '.' + this_compressed_extension
1254 if filename not in installed_files:
1255 continue
1256
1257 # Make sure that all the values in the compression map have the same
1258 # extension. We don't support multiple compression methods in the same
1259 # system image.
1260 if compressed_extension:
1261 if this_compressed_extension != compressed_extension:
1262 raise ValueError(
1263 "Multiple compressed extensions: {} vs {}".format(
1264 compressed_extension, this_compressed_extension))
1265 else:
1266 compressed_extension = this_compressed_extension
1267
1268 return (certmap,
1269 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001270
1271
Doug Zongkereef39442009-04-02 12:14:19 -07001272COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001273Global options
1274
1275 -p (--path) <dir>
1276 Prepend <dir>/bin to the list of places to search for binaries run by this
1277 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001278
Doug Zongker05d3dea2009-06-22 11:32:31 -07001279 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001280 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001281
Tao Bao30df8b42018-04-23 15:32:53 -07001282 -x (--extra) <key=value>
1283 Add a key/value pair to the 'extras' dict, which device-specific extension
1284 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001285
Doug Zongkereef39442009-04-02 12:14:19 -07001286 -v (--verbose)
1287 Show command lines being executed.
1288
1289 -h (--help)
1290 Display this usage message and exit.
1291"""
1292
1293def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001294 print(docstring.rstrip("\n"))
1295 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001296
1297
1298def ParseOptions(argv,
1299 docstring,
1300 extra_opts="", extra_long_opts=(),
1301 extra_option_handler=None):
1302 """Parse the options in argv and return any arguments that aren't
1303 flags. docstring is the calling module's docstring, to be displayed
1304 for errors and -h. extra_opts and extra_long_opts are for flags
1305 defined by the caller, which are processed by passing them to
1306 extra_option_handler."""
1307
1308 try:
1309 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001310 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001311 ["help", "verbose", "path=", "signapk_path=",
1312 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001313 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001314 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1315 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001316 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001317 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001318 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001319 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001320 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001321 sys.exit(2)
1322
Doug Zongkereef39442009-04-02 12:14:19 -07001323 for o, a in opts:
1324 if o in ("-h", "--help"):
1325 Usage(docstring)
1326 sys.exit()
1327 elif o in ("-v", "--verbose"):
1328 OPTIONS.verbose = True
1329 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001330 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001331 elif o in ("--signapk_path",):
1332 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001333 elif o in ("--signapk_shared_library_path",):
1334 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001335 elif o in ("--extra_signapk_args",):
1336 OPTIONS.extra_signapk_args = shlex.split(a)
1337 elif o in ("--java_path",):
1338 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001339 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001340 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001341 elif o in ("--public_key_suffix",):
1342 OPTIONS.public_key_suffix = a
1343 elif o in ("--private_key_suffix",):
1344 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001345 elif o in ("--boot_signer_path",):
1346 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001347 elif o in ("--boot_signer_args",):
1348 OPTIONS.boot_signer_args = shlex.split(a)
1349 elif o in ("--verity_signer_path",):
1350 OPTIONS.verity_signer_path = a
1351 elif o in ("--verity_signer_args",):
1352 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001353 elif o in ("-s", "--device_specific"):
1354 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001355 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001356 key, value = a.split("=", 1)
1357 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001358 else:
1359 if extra_option_handler is None or not extra_option_handler(o, a):
1360 assert False, "unknown option \"%s\"" % (o,)
1361
Doug Zongker85448772014-09-09 14:59:20 -07001362 if OPTIONS.search_path:
1363 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1364 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001365
1366 return args
1367
1368
Tao Bao4c851b12016-09-19 13:54:38 -07001369def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001370 """Make a temp file and add it to the list of things to be deleted
1371 when Cleanup() is called. Return the filename."""
1372 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1373 os.close(fd)
1374 OPTIONS.tempfiles.append(fn)
1375 return fn
1376
1377
Tao Bao1c830bf2017-12-25 10:43:47 -08001378def MakeTempDir(prefix='tmp', suffix=''):
1379 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1380
1381 Returns:
1382 The absolute pathname of the new directory.
1383 """
1384 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1385 OPTIONS.tempfiles.append(dir_name)
1386 return dir_name
1387
1388
Doug Zongkereef39442009-04-02 12:14:19 -07001389def Cleanup():
1390 for i in OPTIONS.tempfiles:
1391 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001392 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001393 else:
1394 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001395 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001396
1397
1398class PasswordManager(object):
1399 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001400 self.editor = os.getenv("EDITOR")
1401 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001402
1403 def GetPasswords(self, items):
1404 """Get passwords corresponding to each string in 'items',
1405 returning a dict. (The dict may have keys in addition to the
1406 values in 'items'.)
1407
1408 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1409 user edit that file to add more needed passwords. If no editor is
1410 available, or $ANDROID_PW_FILE isn't define, prompts the user
1411 interactively in the ordinary way.
1412 """
1413
1414 current = self.ReadFile()
1415
1416 first = True
1417 while True:
1418 missing = []
1419 for i in items:
1420 if i not in current or not current[i]:
1421 missing.append(i)
1422 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001423 if not missing:
1424 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001425
1426 for i in missing:
1427 current[i] = ""
1428
1429 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001430 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001431 answer = raw_input("try to edit again? [y]> ").strip()
1432 if answer and answer[0] not in 'yY':
1433 raise RuntimeError("key passwords unavailable")
1434 first = False
1435
1436 current = self.UpdateAndReadFile(current)
1437
Dan Albert8b72aef2015-03-23 19:13:21 -07001438 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001439 """Prompt the user to enter a value (password) for each key in
1440 'current' whose value is fales. Returns a new dict with all the
1441 values.
1442 """
1443 result = {}
1444 for k, v in sorted(current.iteritems()):
1445 if v:
1446 result[k] = v
1447 else:
1448 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001449 result[k] = getpass.getpass(
1450 "Enter password for %s key> " % k).strip()
1451 if result[k]:
1452 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001453 return result
1454
1455 def UpdateAndReadFile(self, current):
1456 if not self.editor or not self.pwfile:
1457 return self.PromptResult(current)
1458
1459 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001460 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001461 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1462 f.write("# (Additional spaces are harmless.)\n\n")
1463
1464 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001465 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1466 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001467 f.write("[[[ %s ]]] %s\n" % (v, k))
1468 if not v and first_line is None:
1469 # position cursor on first line with no password.
1470 first_line = i + 4
1471 f.close()
1472
Tao Bao986ee862018-10-04 15:46:16 -07001473 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001474
1475 return self.ReadFile()
1476
1477 def ReadFile(self):
1478 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001479 if self.pwfile is None:
1480 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001481 try:
1482 f = open(self.pwfile, "r")
1483 for line in f:
1484 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001485 if not line or line[0] == '#':
1486 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001487 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1488 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001489 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001490 else:
1491 result[m.group(2)] = m.group(1)
1492 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001493 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001494 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001495 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001496 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001497
1498
Dan Albert8e0178d2015-01-27 15:53:15 -08001499def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1500 compress_type=None):
1501 import datetime
1502
1503 # http://b/18015246
1504 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1505 # for files larger than 2GiB. We can work around this by adjusting their
1506 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1507 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1508 # it isn't clear to me exactly what circumstances cause this).
1509 # `zipfile.write()` must be used directly to work around this.
1510 #
1511 # This mess can be avoided if we port to python3.
1512 saved_zip64_limit = zipfile.ZIP64_LIMIT
1513 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1514
1515 if compress_type is None:
1516 compress_type = zip_file.compression
1517 if arcname is None:
1518 arcname = filename
1519
1520 saved_stat = os.stat(filename)
1521
1522 try:
1523 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1524 # file to be zipped and reset it when we're done.
1525 os.chmod(filename, perms)
1526
1527 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001528 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1529 # intentional. zip stores datetimes in local time without a time zone
1530 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1531 # in the zip archive.
1532 local_epoch = datetime.datetime.fromtimestamp(0)
1533 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001534 os.utime(filename, (timestamp, timestamp))
1535
1536 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1537 finally:
1538 os.chmod(filename, saved_stat.st_mode)
1539 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1540 zipfile.ZIP64_LIMIT = saved_zip64_limit
1541
1542
Tao Bao58c1b962015-05-20 09:32:18 -07001543def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001544 compress_type=None):
1545 """Wrap zipfile.writestr() function to work around the zip64 limit.
1546
1547 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1548 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1549 when calling crc32(bytes).
1550
1551 But it still works fine to write a shorter string into a large zip file.
1552 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1553 when we know the string won't be too long.
1554 """
1555
1556 saved_zip64_limit = zipfile.ZIP64_LIMIT
1557 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1558
1559 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1560 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001561 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001562 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001563 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001564 else:
Tao Baof3282b42015-04-01 11:21:55 -07001565 zinfo = zinfo_or_arcname
1566
1567 # If compress_type is given, it overrides the value in zinfo.
1568 if compress_type is not None:
1569 zinfo.compress_type = compress_type
1570
Tao Bao58c1b962015-05-20 09:32:18 -07001571 # If perms is given, it has a priority.
1572 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001573 # If perms doesn't set the file type, mark it as a regular file.
1574 if perms & 0o770000 == 0:
1575 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001576 zinfo.external_attr = perms << 16
1577
Tao Baof3282b42015-04-01 11:21:55 -07001578 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001579 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1580
Dan Albert8b72aef2015-03-23 19:13:21 -07001581 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001582 zipfile.ZIP64_LIMIT = saved_zip64_limit
1583
1584
Tao Bao89d7ab22017-12-14 17:05:33 -08001585def ZipDelete(zip_filename, entries):
1586 """Deletes entries from a ZIP file.
1587
1588 Since deleting entries from a ZIP file is not supported, it shells out to
1589 'zip -d'.
1590
1591 Args:
1592 zip_filename: The name of the ZIP file.
1593 entries: The name of the entry, or the list of names to be deleted.
1594
1595 Raises:
1596 AssertionError: In case of non-zero return from 'zip'.
1597 """
1598 if isinstance(entries, basestring):
1599 entries = [entries]
1600 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001601 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001602
1603
Tao Baof3282b42015-04-01 11:21:55 -07001604def ZipClose(zip_file):
1605 # http://b/18015246
1606 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1607 # central directory.
1608 saved_zip64_limit = zipfile.ZIP64_LIMIT
1609 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1610
1611 zip_file.close()
1612
1613 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001614
1615
1616class DeviceSpecificParams(object):
1617 module = None
1618 def __init__(self, **kwargs):
1619 """Keyword arguments to the constructor become attributes of this
1620 object, which is passed to all functions in the device-specific
1621 module."""
1622 for k, v in kwargs.iteritems():
1623 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001624 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001625
1626 if self.module is None:
1627 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001628 if not path:
1629 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001630 try:
1631 if os.path.isdir(path):
1632 info = imp.find_module("releasetools", [path])
1633 else:
1634 d, f = os.path.split(path)
1635 b, x = os.path.splitext(f)
1636 if x == ".py":
1637 f = b
1638 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001639 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001640 self.module = imp.load_module("device_specific", *info)
1641 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001642 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001643
1644 def _DoCall(self, function_name, *args, **kwargs):
1645 """Call the named function in the device-specific module, passing
1646 the given args and kwargs. The first argument to the call will be
1647 the DeviceSpecific object itself. If there is no module, or the
1648 module does not define the function, return the value of the
1649 'default' kwarg (which itself defaults to None)."""
1650 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001651 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001652 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1653
1654 def FullOTA_Assertions(self):
1655 """Called after emitting the block of assertions at the top of a
1656 full OTA package. Implementations can add whatever additional
1657 assertions they like."""
1658 return self._DoCall("FullOTA_Assertions")
1659
Doug Zongkere5ff5902012-01-17 10:55:37 -08001660 def FullOTA_InstallBegin(self):
1661 """Called at the start of full OTA installation."""
1662 return self._DoCall("FullOTA_InstallBegin")
1663
Yifan Hong10c530d2018-12-27 17:34:18 -08001664 def FullOTA_GetBlockDifferences(self):
1665 """Called during full OTA installation and verification.
1666 Implementation should return a list of BlockDifference objects describing
1667 the update on each additional partitions.
1668 """
1669 return self._DoCall("FullOTA_GetBlockDifferences")
1670
Doug Zongker05d3dea2009-06-22 11:32:31 -07001671 def FullOTA_InstallEnd(self):
1672 """Called at the end of full OTA installation; typically this is
1673 used to install the image for the device's baseband processor."""
1674 return self._DoCall("FullOTA_InstallEnd")
1675
1676 def IncrementalOTA_Assertions(self):
1677 """Called after emitting the block of assertions at the top of an
1678 incremental OTA package. Implementations can add whatever
1679 additional assertions they like."""
1680 return self._DoCall("IncrementalOTA_Assertions")
1681
Doug Zongkere5ff5902012-01-17 10:55:37 -08001682 def IncrementalOTA_VerifyBegin(self):
1683 """Called at the start of the verification phase of incremental
1684 OTA installation; additional checks can be placed here to abort
1685 the script before any changes are made."""
1686 return self._DoCall("IncrementalOTA_VerifyBegin")
1687
Doug Zongker05d3dea2009-06-22 11:32:31 -07001688 def IncrementalOTA_VerifyEnd(self):
1689 """Called at the end of the verification phase of incremental OTA
1690 installation; additional checks can be placed here to abort the
1691 script before any changes are made."""
1692 return self._DoCall("IncrementalOTA_VerifyEnd")
1693
Doug Zongkere5ff5902012-01-17 10:55:37 -08001694 def IncrementalOTA_InstallBegin(self):
1695 """Called at the start of incremental OTA installation (after
1696 verification is complete)."""
1697 return self._DoCall("IncrementalOTA_InstallBegin")
1698
Yifan Hong10c530d2018-12-27 17:34:18 -08001699 def IncrementalOTA_GetBlockDifferences(self):
1700 """Called during incremental OTA installation and verification.
1701 Implementation should return a list of BlockDifference objects describing
1702 the update on each additional partitions.
1703 """
1704 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1705
Doug Zongker05d3dea2009-06-22 11:32:31 -07001706 def IncrementalOTA_InstallEnd(self):
1707 """Called at the end of incremental OTA installation; typically
1708 this is used to install the image for the device's baseband
1709 processor."""
1710 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001711
Tao Bao9bc6bb22015-11-09 16:58:28 -08001712 def VerifyOTA_Assertions(self):
1713 return self._DoCall("VerifyOTA_Assertions")
1714
Tao Bao76def242017-11-21 09:25:31 -08001715
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001716class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001717 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001718 self.name = name
1719 self.data = data
1720 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001721 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001722 self.sha1 = sha1(data).hexdigest()
1723
1724 @classmethod
1725 def FromLocalFile(cls, name, diskname):
1726 f = open(diskname, "rb")
1727 data = f.read()
1728 f.close()
1729 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001730
1731 def WriteToTemp(self):
1732 t = tempfile.NamedTemporaryFile()
1733 t.write(self.data)
1734 t.flush()
1735 return t
1736
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001737 def WriteToDir(self, d):
1738 with open(os.path.join(d, self.name), "wb") as fp:
1739 fp.write(self.data)
1740
Geremy Condra36bd3652014-02-06 19:45:10 -08001741 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001742 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001743
Tao Bao76def242017-11-21 09:25:31 -08001744
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001745DIFF_PROGRAM_BY_EXT = {
1746 ".gz" : "imgdiff",
1747 ".zip" : ["imgdiff", "-z"],
1748 ".jar" : ["imgdiff", "-z"],
1749 ".apk" : ["imgdiff", "-z"],
1750 ".img" : "imgdiff",
1751 }
1752
Tao Bao76def242017-11-21 09:25:31 -08001753
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001754class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001755 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001756 self.tf = tf
1757 self.sf = sf
1758 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001759 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001760
1761 def ComputePatch(self):
1762 """Compute the patch (as a string of data) needed to turn sf into
1763 tf. Returns the same tuple as GetPatch()."""
1764
1765 tf = self.tf
1766 sf = self.sf
1767
Doug Zongker24cd2802012-08-14 16:36:15 -07001768 if self.diff_program:
1769 diff_program = self.diff_program
1770 else:
1771 ext = os.path.splitext(tf.name)[1]
1772 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001773
1774 ttemp = tf.WriteToTemp()
1775 stemp = sf.WriteToTemp()
1776
1777 ext = os.path.splitext(tf.name)[1]
1778
1779 try:
1780 ptemp = tempfile.NamedTemporaryFile()
1781 if isinstance(diff_program, list):
1782 cmd = copy.copy(diff_program)
1783 else:
1784 cmd = [diff_program]
1785 cmd.append(stemp.name)
1786 cmd.append(ttemp.name)
1787 cmd.append(ptemp.name)
1788 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001789 err = []
1790 def run():
1791 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001792 if e:
1793 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001794 th = threading.Thread(target=run)
1795 th.start()
1796 th.join(timeout=300) # 5 mins
1797 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001798 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001799 p.terminate()
1800 th.join(5)
1801 if th.is_alive():
1802 p.kill()
1803 th.join()
1804
Tianjie Xua2a9f992018-01-05 15:15:54 -08001805 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001806 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001807 self.patch = None
1808 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001809 diff = ptemp.read()
1810 finally:
1811 ptemp.close()
1812 stemp.close()
1813 ttemp.close()
1814
1815 self.patch = diff
1816 return self.tf, self.sf, self.patch
1817
1818
1819 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001820 """Returns a tuple of (target_file, source_file, patch_data).
1821
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001822 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001823 computing the patch failed.
1824 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001825 return self.tf, self.sf, self.patch
1826
1827
1828def ComputeDifferences(diffs):
1829 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001830 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001831
1832 # Do the largest files first, to try and reduce the long-pole effect.
1833 by_size = [(i.tf.size, i) for i in diffs]
1834 by_size.sort(reverse=True)
1835 by_size = [i[1] for i in by_size]
1836
1837 lock = threading.Lock()
1838 diff_iter = iter(by_size) # accessed under lock
1839
1840 def worker():
1841 try:
1842 lock.acquire()
1843 for d in diff_iter:
1844 lock.release()
1845 start = time.time()
1846 d.ComputePatch()
1847 dur = time.time() - start
1848 lock.acquire()
1849
1850 tf, sf, patch = d.GetPatch()
1851 if sf.name == tf.name:
1852 name = tf.name
1853 else:
1854 name = "%s (%s)" % (tf.name, sf.name)
1855 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001856 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001857 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001858 logger.info(
1859 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1860 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001861 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001862 except Exception:
1863 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001864 raise
1865
1866 # start worker threads; wait for them all to finish.
1867 threads = [threading.Thread(target=worker)
1868 for i in range(OPTIONS.worker_threads)]
1869 for th in threads:
1870 th.start()
1871 while threads:
1872 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001873
1874
Dan Albert8b72aef2015-03-23 19:13:21 -07001875class BlockDifference(object):
1876 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001877 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001878 self.tgt = tgt
1879 self.src = src
1880 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001881 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001882 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001883
Tao Baodd2a5892015-03-12 12:32:37 -07001884 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001885 version = max(
1886 int(i) for i in
1887 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001888 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001889 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001890
1891 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001892 version=self.version,
1893 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001894 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001895 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001896 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001897 self.touched_src_ranges = b.touched_src_ranges
1898 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001899
Yifan Hong10c530d2018-12-27 17:34:18 -08001900 # On devices with dynamic partitions, for new partitions,
1901 # src is None but OPTIONS.source_info_dict is not.
1902 if OPTIONS.source_info_dict is None:
1903 is_dynamic_build = OPTIONS.info_dict.get(
1904 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001905 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001906 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001907 is_dynamic_build = OPTIONS.source_info_dict.get(
1908 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001909 is_dynamic_source = partition in shlex.split(
1910 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001911
Yifan Hongbb2658d2019-01-25 12:30:58 -08001912 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001913 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1914
Yifan Hongbb2658d2019-01-25 12:30:58 -08001915 # For dynamic partitions builds, check partition list in both source
1916 # and target build because new partitions may be added, and existing
1917 # partitions may be removed.
1918 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1919
Yifan Hong10c530d2018-12-27 17:34:18 -08001920 if is_dynamic:
1921 self.device = 'map_partition("%s")' % partition
1922 else:
1923 if OPTIONS.source_info_dict is None:
1924 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1925 else:
1926 _, device_path = GetTypeAndDevice("/" + partition,
1927 OPTIONS.source_info_dict)
1928 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001929
Tao Baod8d14be2016-02-04 14:26:02 -08001930 @property
1931 def required_cache(self):
1932 return self._required_cache
1933
Tao Bao76def242017-11-21 09:25:31 -08001934 def WriteScript(self, script, output_zip, progress=None,
1935 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001936 if not self.src:
1937 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001938 script.Print("Patching %s image unconditionally..." % (self.partition,))
1939 else:
1940 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001941
Dan Albert8b72aef2015-03-23 19:13:21 -07001942 if progress:
1943 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001944 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001945
1946 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001947 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001948
Tao Bao9bc6bb22015-11-09 16:58:28 -08001949 def WriteStrictVerifyScript(self, script):
1950 """Verify all the blocks in the care_map, including clobbered blocks.
1951
1952 This differs from the WriteVerifyScript() function: a) it prints different
1953 error messages; b) it doesn't allow half-way updated images to pass the
1954 verification."""
1955
1956 partition = self.partition
1957 script.Print("Verifying %s..." % (partition,))
1958 ranges = self.tgt.care_map
1959 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001960 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001961 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1962 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001963 self.device, ranges_str,
1964 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001965 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001966 script.AppendExtra("")
1967
Tao Baod522bdc2016-04-12 15:53:16 -07001968 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001969 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001970
1971 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001972 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001973 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001974
1975 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001976 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001977 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001978 ranges = self.touched_src_ranges
1979 expected_sha1 = self.touched_src_sha1
1980 else:
1981 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1982 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001983
1984 # No blocks to be checked, skipping.
1985 if not ranges:
1986 return
1987
Tao Bao5ece99d2015-05-12 11:42:31 -07001988 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001989 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001990 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001991 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1992 '"%s.patch.dat")) then' % (
1993 self.device, ranges_str, expected_sha1,
1994 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001995 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001996 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001997
Tianjie Xufc3422a2015-12-15 11:53:59 -08001998 if self.version >= 4:
1999
2000 # Bug: 21124327
2001 # When generating incrementals for the system and vendor partitions in
2002 # version 4 or newer, explicitly check the first block (which contains
2003 # the superblock) of the partition to see if it's what we expect. If
2004 # this check fails, give an explicit log message about the partition
2005 # having been remounted R/W (the most likely explanation).
2006 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002007 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002008
2009 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002010 if partition == "system":
2011 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2012 else:
2013 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002014 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002015 'ifelse (block_image_recover({device}, "{ranges}") && '
2016 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002017 'package_extract_file("{partition}.transfer.list"), '
2018 '"{partition}.new.dat", "{partition}.patch.dat"), '
2019 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002020 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002021 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002022 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002023
Tao Baodd2a5892015-03-12 12:32:37 -07002024 # Abort the OTA update. Note that the incremental OTA cannot be applied
2025 # even if it may match the checksum of the target partition.
2026 # a) If version < 3, operations like move and erase will make changes
2027 # unconditionally and damage the partition.
2028 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002029 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002030 if partition == "system":
2031 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2032 else:
2033 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2034 script.AppendExtra((
2035 'abort("E%d: %s partition has unexpected contents");\n'
2036 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002037
Yifan Hong10c530d2018-12-27 17:34:18 -08002038 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002039 partition = self.partition
2040 script.Print('Verifying the updated %s image...' % (partition,))
2041 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2042 ranges = self.tgt.care_map
2043 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002044 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002045 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002046 self.device, ranges_str,
2047 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002048
2049 # Bug: 20881595
2050 # Verify that extended blocks are really zeroed out.
2051 if self.tgt.extended:
2052 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002053 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002054 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002055 self.device, ranges_str,
2056 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002057 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002058 if partition == "system":
2059 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2060 else:
2061 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002062 script.AppendExtra(
2063 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002064 ' abort("E%d: %s partition has unexpected non-zero contents after '
2065 'OTA update");\n'
2066 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002067 else:
2068 script.Print('Verified the updated %s image.' % (partition,))
2069
Tianjie Xu209db462016-05-24 17:34:52 -07002070 if partition == "system":
2071 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2072 else:
2073 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2074
Tao Bao5fcaaef2015-06-01 13:40:49 -07002075 script.AppendExtra(
2076 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002077 ' abort("E%d: %s partition has unexpected contents after OTA '
2078 'update");\n'
2079 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002080
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002081 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002082 ZipWrite(output_zip,
2083 '{}.transfer.list'.format(self.path),
2084 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002085
Tao Bao76def242017-11-21 09:25:31 -08002086 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2087 # its size. Quailty 9 almost triples the compression time but doesn't
2088 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002089 # zip | brotli(quality 6) | brotli(quality 9)
2090 # compressed_size: 942M | 869M (~8% reduced) | 854M
2091 # compression_time: 75s | 265s | 719s
2092 # decompression_time: 15s | 25s | 25s
2093
2094 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002095 brotli_cmd = ['brotli', '--quality=6',
2096 '--output={}.new.dat.br'.format(self.path),
2097 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002098 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002099 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002100
2101 new_data_name = '{}.new.dat.br'.format(self.partition)
2102 ZipWrite(output_zip,
2103 '{}.new.dat.br'.format(self.path),
2104 new_data_name,
2105 compress_type=zipfile.ZIP_STORED)
2106 else:
2107 new_data_name = '{}.new.dat'.format(self.partition)
2108 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2109
Dan Albert8e0178d2015-01-27 15:53:15 -08002110 ZipWrite(output_zip,
2111 '{}.patch.dat'.format(self.path),
2112 '{}.patch.dat'.format(self.partition),
2113 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002114
Tianjie Xu209db462016-05-24 17:34:52 -07002115 if self.partition == "system":
2116 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2117 else:
2118 code = ErrorCode.VENDOR_UPDATE_FAILURE
2119
Yifan Hong10c530d2018-12-27 17:34:18 -08002120 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002121 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002122 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002123 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002124 device=self.device, partition=self.partition,
2125 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002126 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002127
Dan Albert8b72aef2015-03-23 19:13:21 -07002128 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002129 data = source.ReadRangeSet(ranges)
2130 ctx = sha1()
2131
2132 for p in data:
2133 ctx.update(p)
2134
2135 return ctx.hexdigest()
2136
Tao Baoe9b61912015-07-09 17:37:49 -07002137 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2138 """Return the hash value for all zero blocks."""
2139 zero_block = '\x00' * 4096
2140 ctx = sha1()
2141 for _ in range(num_blocks):
2142 ctx.update(zero_block)
2143
2144 return ctx.hexdigest()
2145
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002146
2147DataImage = blockimgdiff.DataImage
Yifan Hong8a66a712019-04-04 15:37:57 -07002148EmptyImage = blockimgdiff.EmptyImage
Tao Bao76def242017-11-21 09:25:31 -08002149
Doug Zongker96a57e72010-09-26 14:57:41 -07002150# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002151PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002152 "ext4": "EMMC",
2153 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002154 "f2fs": "EMMC",
2155 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002156}
Doug Zongker96a57e72010-09-26 14:57:41 -07002157
Tao Bao76def242017-11-21 09:25:31 -08002158
Doug Zongker96a57e72010-09-26 14:57:41 -07002159def GetTypeAndDevice(mount_point, info):
2160 fstab = info["fstab"]
2161 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002162 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2163 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002164 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002165 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002166
2167
2168def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002169 """Parses and converts a PEM-encoded certificate into DER-encoded.
2170
2171 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2172
2173 Returns:
2174 The decoded certificate string.
2175 """
2176 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002177 save = False
2178 for line in data.split("\n"):
2179 if "--END CERTIFICATE--" in line:
2180 break
2181 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002182 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002183 if "--BEGIN CERTIFICATE--" in line:
2184 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002185 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002186 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002187
Tao Bao04e1f012018-02-04 12:13:35 -08002188
2189def ExtractPublicKey(cert):
2190 """Extracts the public key (PEM-encoded) from the given certificate file.
2191
2192 Args:
2193 cert: The certificate filename.
2194
2195 Returns:
2196 The public key string.
2197
2198 Raises:
2199 AssertionError: On non-zero return from 'openssl'.
2200 """
2201 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2202 # While openssl 1.1 writes the key into the given filename followed by '-out',
2203 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2204 # stdout instead.
2205 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2206 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2207 pubkey, stderrdata = proc.communicate()
2208 assert proc.returncode == 0, \
2209 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2210 return pubkey
2211
2212
Tao Bao2cc0ca12019-03-15 10:44:43 -07002213def ExtractAvbPublicKey(key):
2214 """Extracts the AVB public key from the given public or private key.
2215
2216 Args:
2217 key: The input key file, which should be PEM-encoded public or private key.
2218
2219 Returns:
2220 The path to the extracted AVB public key file.
2221 """
2222 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2223 RunAndCheckOutput(
2224 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2225 return output
2226
2227
Doug Zongker412c02f2014-02-13 10:58:24 -08002228def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2229 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002230 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002231
Tao Bao6d5d6232018-03-09 17:04:42 -08002232 Most of the space in the boot and recovery images is just the kernel, which is
2233 identical for the two, so the resulting patch should be efficient. Add it to
2234 the output zip, along with a shell script that is run from init.rc on first
2235 boot to actually do the patching and install the new recovery image.
2236
2237 Args:
2238 input_dir: The top-level input directory of the target-files.zip.
2239 output_sink: The callback function that writes the result.
2240 recovery_img: File object for the recovery image.
2241 boot_img: File objects for the boot image.
2242 info_dict: A dict returned by common.LoadInfoDict() on the input
2243 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002244 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002245 if info_dict is None:
2246 info_dict = OPTIONS.info_dict
2247
Tao Bao6d5d6232018-03-09 17:04:42 -08002248 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002249
Tao Baof2cffbd2015-07-22 12:33:18 -07002250 if full_recovery_image:
2251 output_sink("etc/recovery.img", recovery_img.data)
2252
2253 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002254 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002255 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002256 # With system-root-image, boot and recovery images will have mismatching
2257 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2258 # to handle such a case.
2259 if system_root_image:
2260 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002261 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002262 assert not os.path.exists(path)
2263 else:
2264 diff_program = ["imgdiff"]
2265 if os.path.exists(path):
2266 diff_program.append("-b")
2267 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002268 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002269 else:
2270 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002271
2272 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2273 _, _, patch = d.ComputePatch()
2274 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002275
Dan Albertebb19aa2015-03-27 19:11:53 -07002276 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002277 # The following GetTypeAndDevice()s need to use the path in the target
2278 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002279 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2280 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2281 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002282 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002283
Tao Baof2cffbd2015-07-22 12:33:18 -07002284 if full_recovery_image:
2285 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002286if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2287 applypatch \\
2288 --flash /system/etc/recovery.img \\
2289 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2290 log -t recovery "Installing new recovery image: succeeded" || \\
2291 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002292else
2293 log -t recovery "Recovery image already installed"
2294fi
2295""" % {'type': recovery_type,
2296 'device': recovery_device,
2297 'sha1': recovery_img.sha1,
2298 'size': recovery_img.size}
2299 else:
2300 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002301if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2302 applypatch %(bonus_args)s \\
2303 --patch /system/recovery-from-boot.p \\
2304 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2305 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2306 log -t recovery "Installing new recovery image: succeeded" || \\
2307 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002308else
2309 log -t recovery "Recovery image already installed"
2310fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002311""" % {'boot_size': boot_img.size,
2312 'boot_sha1': boot_img.sha1,
2313 'recovery_size': recovery_img.size,
2314 'recovery_sha1': recovery_img.sha1,
2315 'boot_type': boot_type,
2316 'boot_device': boot_device,
2317 'recovery_type': recovery_type,
2318 'recovery_device': recovery_device,
2319 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002320
2321 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002322 # in the L release.
2323 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002324
Tao Bao32fcdab2018-10-12 10:30:39 -07002325 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002326
2327 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002328
2329
2330class DynamicPartitionUpdate(object):
2331 def __init__(self, src_group=None, tgt_group=None, progress=None,
2332 block_difference=None):
2333 self.src_group = src_group
2334 self.tgt_group = tgt_group
2335 self.progress = progress
2336 self.block_difference = block_difference
2337
2338 @property
2339 def src_size(self):
2340 if not self.block_difference:
2341 return 0
2342 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2343
2344 @property
2345 def tgt_size(self):
2346 if not self.block_difference:
2347 return 0
2348 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2349
2350 @staticmethod
2351 def _GetSparseImageSize(img):
2352 if not img:
2353 return 0
2354 return img.blocksize * img.total_blocks
2355
2356
2357class DynamicGroupUpdate(object):
2358 def __init__(self, src_size=None, tgt_size=None):
2359 # None: group does not exist. 0: no size limits.
2360 self.src_size = src_size
2361 self.tgt_size = tgt_size
2362
2363
2364class DynamicPartitionsDifference(object):
2365 def __init__(self, info_dict, block_diffs, progress_dict=None,
2366 source_info_dict=None):
2367 if progress_dict is None:
2368 progress_dict = dict()
2369
2370 self._remove_all_before_apply = False
2371 if source_info_dict is None:
2372 self._remove_all_before_apply = True
2373 source_info_dict = dict()
2374
2375 block_diff_dict = {e.partition:e for e in block_diffs}
2376 assert len(block_diff_dict) == len(block_diffs), \
2377 "Duplicated BlockDifference object for {}".format(
2378 [partition for partition, count in
2379 collections.Counter(e.partition for e in block_diffs).items()
2380 if count > 1])
2381
Yifan Hong79997e52019-01-23 16:56:19 -08002382 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002383
2384 for p, block_diff in block_diff_dict.items():
2385 self._partition_updates[p] = DynamicPartitionUpdate()
2386 self._partition_updates[p].block_difference = block_diff
2387
2388 for p, progress in progress_dict.items():
2389 if p in self._partition_updates:
2390 self._partition_updates[p].progress = progress
2391
2392 tgt_groups = shlex.split(info_dict.get(
2393 "super_partition_groups", "").strip())
2394 src_groups = shlex.split(source_info_dict.get(
2395 "super_partition_groups", "").strip())
2396
2397 for g in tgt_groups:
2398 for p in shlex.split(info_dict.get(
2399 "super_%s_partition_list" % g, "").strip()):
2400 assert p in self._partition_updates, \
2401 "{} is in target super_{}_partition_list but no BlockDifference " \
2402 "object is provided.".format(p, g)
2403 self._partition_updates[p].tgt_group = g
2404
2405 for g in src_groups:
2406 for p in shlex.split(source_info_dict.get(
2407 "super_%s_partition_list" % g, "").strip()):
2408 assert p in self._partition_updates, \
2409 "{} is in source super_{}_partition_list but no BlockDifference " \
2410 "object is provided.".format(p, g)
2411 self._partition_updates[p].src_group = g
2412
Yifan Hong45433e42019-01-18 13:55:25 -08002413 target_dynamic_partitions = set(shlex.split(info_dict.get(
2414 "dynamic_partition_list", "").strip()))
2415 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2416 if u.tgt_size)
2417 assert block_diffs_with_target == target_dynamic_partitions, \
2418 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2419 list(target_dynamic_partitions), list(block_diffs_with_target))
2420
2421 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2422 "dynamic_partition_list", "").strip()))
2423 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2424 if u.src_size)
2425 assert block_diffs_with_source == source_dynamic_partitions, \
2426 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2427 list(source_dynamic_partitions), list(block_diffs_with_source))
2428
Yifan Hong10c530d2018-12-27 17:34:18 -08002429 if self._partition_updates:
2430 logger.info("Updating dynamic partitions %s",
2431 self._partition_updates.keys())
2432
Yifan Hong79997e52019-01-23 16:56:19 -08002433 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002434
2435 for g in tgt_groups:
2436 self._group_updates[g] = DynamicGroupUpdate()
2437 self._group_updates[g].tgt_size = int(info_dict.get(
2438 "super_%s_group_size" % g, "0").strip())
2439
2440 for g in src_groups:
2441 if g not in self._group_updates:
2442 self._group_updates[g] = DynamicGroupUpdate()
2443 self._group_updates[g].src_size = int(source_info_dict.get(
2444 "super_%s_group_size" % g, "0").strip())
2445
2446 self._Compute()
2447
2448 def WriteScript(self, script, output_zip, write_verify_script=False):
2449 script.Comment('--- Start patching dynamic partitions ---')
2450 for p, u in self._partition_updates.items():
2451 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2452 script.Comment('Patch partition %s' % p)
2453 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2454 write_verify_script=False)
2455
2456 op_list_path = MakeTempFile()
2457 with open(op_list_path, 'w') as f:
2458 for line in self._op_list:
2459 f.write('{}\n'.format(line))
2460
2461 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2462
2463 script.Comment('Update dynamic partition metadata')
2464 script.AppendExtra('assert(update_dynamic_partitions('
2465 'package_extract_file("dynamic_partitions_op_list")));')
2466
2467 if write_verify_script:
2468 for p, u in self._partition_updates.items():
2469 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2470 u.block_difference.WritePostInstallVerifyScript(script)
2471 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2472
2473 for p, u in self._partition_updates.items():
2474 if u.tgt_size and u.src_size <= u.tgt_size:
2475 script.Comment('Patch partition %s' % p)
2476 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2477 write_verify_script=write_verify_script)
2478 if write_verify_script:
2479 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2480
2481 script.Comment('--- End patching dynamic partitions ---')
2482
2483 def _Compute(self):
2484 self._op_list = list()
2485
2486 def append(line):
2487 self._op_list.append(line)
2488
2489 def comment(line):
2490 self._op_list.append("# %s" % line)
2491
2492 if self._remove_all_before_apply:
2493 comment('Remove all existing dynamic partitions and groups before '
2494 'applying full OTA')
2495 append('remove_all_groups')
2496
2497 for p, u in self._partition_updates.items():
2498 if u.src_group and not u.tgt_group:
2499 append('remove %s' % p)
2500
2501 for p, u in self._partition_updates.items():
2502 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2503 comment('Move partition %s from %s to default' % (p, u.src_group))
2504 append('move %s default' % p)
2505
2506 for p, u in self._partition_updates.items():
2507 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2508 comment('Shrink partition %s from %d to %d' %
2509 (p, u.src_size, u.tgt_size))
2510 append('resize %s %s' % (p, u.tgt_size))
2511
2512 for g, u in self._group_updates.items():
2513 if u.src_size is not None and u.tgt_size is None:
2514 append('remove_group %s' % g)
2515 if (u.src_size is not None and u.tgt_size is not None and
2516 u.src_size > u.tgt_size):
2517 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2518 append('resize_group %s %d' % (g, u.tgt_size))
2519
2520 for g, u in self._group_updates.items():
2521 if u.src_size is None and u.tgt_size is not None:
2522 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2523 append('add_group %s %d' % (g, u.tgt_size))
2524 if (u.src_size is not None and u.tgt_size is not None and
2525 u.src_size < u.tgt_size):
2526 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2527 append('resize_group %s %d' % (g, u.tgt_size))
2528
2529 for p, u in self._partition_updates.items():
2530 if u.tgt_group and not u.src_group:
2531 comment('Add partition %s to group %s' % (p, u.tgt_group))
2532 append('add %s %s' % (p, u.tgt_group))
2533
2534 for p, u in self._partition_updates.items():
2535 if u.tgt_size and u.src_size < u.tgt_size:
2536 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2537 append('resize %s %d' % (p, u.tgt_size))
2538
2539 for p, u in self._partition_updates.items():
2540 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2541 comment('Move partition %s from default to %s' %
2542 (p, u.tgt_group))
2543 append('move %s %s' % (p, u.tgt_group))