blob: 5bdb2a70f24023e6f2cf134c458c2aa44b71df1d [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:
Daniel Norman72c626f2019-05-13 15:58:14 -0700335 # "selinux_fc" properties should point to the file_contexts files
336 # (file_contexts.bin) under META/.
337 for key in d:
338 if key.endswith("selinux_fc"):
339 fc_basename = os.path.basename(d[key])
340 fc_config = os.path.join(input_file, "META", fc_basename)
341 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700342
Daniel Norman72c626f2019-05-13 15:58:14 -0700343 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700344
Tom Cherryd14b8952018-08-09 14:26:00 -0700345 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700346 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700347 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700348 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700349
Tao Baof54216f2016-03-29 15:12:37 -0700350 # Redirect {system,vendor}_base_fs_file.
351 if "system_base_fs_file" in d:
352 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700353 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700354 if os.path.exists(system_base_fs_file):
355 d["system_base_fs_file"] = system_base_fs_file
356 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700357 logger.warning(
358 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700359 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700360
361 if "vendor_base_fs_file" in d:
362 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700363 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700364 if os.path.exists(vendor_base_fs_file):
365 d["vendor_base_fs_file"] = vendor_base_fs_file
366 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700367 logger.warning(
368 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700369 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700370
Doug Zongker37974732010-09-16 17:44:38 -0700371 def makeint(key):
372 if key in d:
373 d[key] = int(d[key], 0)
374
375 makeint("recovery_api_version")
376 makeint("blocksize")
377 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700378 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700379 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700380 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700381 makeint("recovery_size")
382 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800383 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700384
Tao Baoa57ab9f2018-08-24 12:08:38 -0700385 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
386 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
387 # cases, since it may load the info_dict from an old build (e.g. when
388 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800389 system_root_image = d.get("system_root_image") == "true"
390 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700391 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700392 if isinstance(input_file, zipfile.ZipFile):
393 if recovery_fstab_path not in input_file.namelist():
394 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
395 else:
396 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
397 if not os.path.exists(path):
398 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800399 d["fstab"] = LoadRecoveryFSTab(
400 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700401
Tao Bao76def242017-11-21 09:25:31 -0800402 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700403 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700404 if isinstance(input_file, zipfile.ZipFile):
405 if recovery_fstab_path not in input_file.namelist():
406 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
407 else:
408 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
409 if not os.path.exists(path):
410 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800411 d["fstab"] = LoadRecoveryFSTab(
412 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700413
Tianjie Xucfa86222016-03-07 16:31:19 -0800414 else:
415 d["fstab"] = None
416
Tianjie Xu861f4132018-09-12 11:49:33 -0700417 # Tries to load the build props for all partitions with care_map, including
418 # system and vendor.
419 for partition in PARTITIONS_WITH_CARE_MAP:
420 d["{}.build.prop".format(partition)] = LoadBuildProp(
421 read_helper, "{}/build.prop".format(partition.upper()))
422 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800423
424 # Set up the salt (based on fingerprint or thumbprint) that will be used when
425 # adding AVB footer.
426 if d.get("avb_enable") == "true":
427 fp = None
428 if "build.prop" in d:
429 build_prop = d["build.prop"]
430 if "ro.build.fingerprint" in build_prop:
431 fp = build_prop["ro.build.fingerprint"]
432 elif "ro.build.thumbprint" in build_prop:
433 fp = build_prop["ro.build.thumbprint"]
434 if fp:
435 d["avb_salt"] = sha256(fp).hexdigest()
436
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700437 return d
438
Tao Baod1de6f32017-03-01 16:38:48 -0800439
Tao Baobcd1d162017-08-26 13:10:26 -0700440def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700441 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700442 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700443 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700444 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700445 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700446 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700447
Tao Baod1de6f32017-03-01 16:38:48 -0800448
Michael Runge6e836112014-04-15 17:40:21 -0700449def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700450 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700451 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700452 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700453 if not line or line.startswith("#"):
454 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700455 if "=" in line:
456 name, value = line.split("=", 1)
457 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700458 return d
459
Tao Baod1de6f32017-03-01 16:38:48 -0800460
Tianjie Xucfa86222016-03-07 16:31:19 -0800461def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
462 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700463 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800464 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700465 self.mount_point = mount_point
466 self.fs_type = fs_type
467 self.device = device
468 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700469 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700470
471 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800472 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700473 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700474 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700475 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700476
Tao Baod1de6f32017-03-01 16:38:48 -0800477 assert fstab_version == 2
478
479 d = {}
480 for line in data.split("\n"):
481 line = line.strip()
482 if not line or line.startswith("#"):
483 continue
484
485 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
486 pieces = line.split()
487 if len(pieces) != 5:
488 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
489
490 # Ignore entries that are managed by vold.
491 options = pieces[4]
492 if "voldmanaged=" in options:
493 continue
494
495 # It's a good line, parse it.
496 length = 0
497 options = options.split(",")
498 for i in options:
499 if i.startswith("length="):
500 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800501 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800502 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700503 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800504
Tao Baod1de6f32017-03-01 16:38:48 -0800505 mount_flags = pieces[3]
506 # Honor the SELinux context if present.
507 context = None
508 for i in mount_flags.split(","):
509 if i.startswith("context="):
510 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800511
Tao Baod1de6f32017-03-01 16:38:48 -0800512 mount_point = pieces[1]
513 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
514 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800515
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700516 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700517 # system. Other areas assume system is always at "/system" so point /system
518 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700519 if system_root_image:
520 assert not d.has_key("/system") and d.has_key("/")
521 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700522 return d
523
524
Doug Zongker37974732010-09-16 17:44:38 -0700525def DumpInfoDict(d):
526 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700527 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700528
Dan Albert8b72aef2015-03-23 19:13:21 -0700529
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800530def AppendAVBSigningArgs(cmd, partition):
531 """Append signing arguments for avbtool."""
532 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
533 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
534 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
535 if key_path and algorithm:
536 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700537 avb_salt = OPTIONS.info_dict.get("avb_salt")
538 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700539 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700540 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800541
542
Tao Bao02a08592018-07-22 12:40:45 -0700543def GetAvbChainedPartitionArg(partition, info_dict, key=None):
544 """Constructs and returns the arg to build or verify a chained partition.
545
546 Args:
547 partition: The partition name.
548 info_dict: The info dict to look up the key info and rollback index
549 location.
550 key: The key to be used for building or verifying the partition. Defaults to
551 the key listed in info_dict.
552
553 Returns:
554 A string of form "partition:rollback_index_location:key" that can be used to
555 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700556 """
557 if key is None:
558 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao2cc0ca12019-03-15 10:44:43 -0700559 pubkey_path = ExtractAvbPublicKey(key)
Tao Bao02a08592018-07-22 12:40:45 -0700560 rollback_index_location = info_dict[
561 "avb_" + partition + "_rollback_index_location"]
562 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
563
564
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700565def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800566 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700567 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700568
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700569 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800570 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
571 we are building a two-step special image (i.e. building a recovery image to
572 be loaded into /boot in two-step OTAs).
573
574 Return the image data, or None if sourcedir does not appear to contains files
575 for building the requested image.
576 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700577
578 def make_ramdisk():
579 ramdisk_img = tempfile.NamedTemporaryFile()
580
581 if os.access(fs_config_file, os.F_OK):
582 cmd = ["mkbootfs", "-f", fs_config_file,
583 os.path.join(sourcedir, "RAMDISK")]
584 else:
585 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
586 p1 = Run(cmd, stdout=subprocess.PIPE)
587 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
588
589 p2.wait()
590 p1.wait()
591 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
592 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
593
594 return ramdisk_img
595
596 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
597 return None
598
599 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700600 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700601
Doug Zongkerd5131602012-08-02 14:46:42 -0700602 if info_dict is None:
603 info_dict = OPTIONS.info_dict
604
Doug Zongkereef39442009-04-02 12:14:19 -0700605 img = tempfile.NamedTemporaryFile()
606
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700607 if has_ramdisk:
608 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700609
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800610 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
611 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
612
613 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700614
Benoit Fradina45a8682014-07-14 21:00:43 +0200615 fn = os.path.join(sourcedir, "second")
616 if os.access(fn, os.F_OK):
617 cmd.append("--second")
618 cmd.append(fn)
619
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800620 fn = os.path.join(sourcedir, "dtb")
621 if os.access(fn, os.F_OK):
622 cmd.append("--dtb")
623 cmd.append(fn)
624
Doug Zongker171f1cd2009-06-15 22:36:37 -0700625 fn = os.path.join(sourcedir, "cmdline")
626 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700627 cmd.append("--cmdline")
628 cmd.append(open(fn).read().rstrip("\n"))
629
630 fn = os.path.join(sourcedir, "base")
631 if os.access(fn, os.F_OK):
632 cmd.append("--base")
633 cmd.append(open(fn).read().rstrip("\n"))
634
Ying Wang4de6b5b2010-08-25 14:29:34 -0700635 fn = os.path.join(sourcedir, "pagesize")
636 if os.access(fn, os.F_OK):
637 cmd.append("--pagesize")
638 cmd.append(open(fn).read().rstrip("\n"))
639
Tao Bao76def242017-11-21 09:25:31 -0800640 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700641 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700642 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700643
Tao Bao76def242017-11-21 09:25:31 -0800644 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000645 if args and args.strip():
646 cmd.extend(shlex.split(args))
647
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700648 if has_ramdisk:
649 cmd.extend(["--ramdisk", ramdisk_img.name])
650
Tao Baod95e9fd2015-03-29 23:07:41 -0700651 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800652 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700653 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700654 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700655 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700656 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700657
Tao Baobf70c3182017-07-11 17:27:55 -0700658 # "boot" or "recovery", without extension.
659 partition_name = os.path.basename(sourcedir).lower()
660
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800661 if partition_name == "recovery":
662 if info_dict.get("include_recovery_dtbo") == "true":
663 fn = os.path.join(sourcedir, "recovery_dtbo")
664 cmd.extend(["--recovery_dtbo", fn])
665 if info_dict.get("include_recovery_acpio") == "true":
666 fn = os.path.join(sourcedir, "recovery_acpio")
667 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700668
Tao Bao986ee862018-10-04 15:46:16 -0700669 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700670
Tao Bao76def242017-11-21 09:25:31 -0800671 if (info_dict.get("boot_signer") == "true" and
672 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800673 # Hard-code the path as "/boot" for two-step special recovery image (which
674 # will be loaded into /boot during the two-step OTA).
675 if two_step_image:
676 path = "/boot"
677 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700678 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700679 cmd = [OPTIONS.boot_signer_path]
680 cmd.extend(OPTIONS.boot_signer_args)
681 cmd.extend([path, img.name,
682 info_dict["verity_key"] + ".pk8",
683 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700684 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700685
Tao Baod95e9fd2015-03-29 23:07:41 -0700686 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800687 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700688 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700689 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800690 # We have switched from the prebuilt futility binary to using the tool
691 # (futility-host) built from the source. Override the setting in the old
692 # TF.zip.
693 futility = info_dict["futility"]
694 if futility.startswith("prebuilts/"):
695 futility = "futility-host"
696 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700697 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700698 info_dict["vboot_key"] + ".vbprivk",
699 info_dict["vboot_subkey"] + ".vbprivk",
700 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700701 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700702 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700703
Tao Baof3282b42015-04-01 11:21:55 -0700704 # Clean up the temp files.
705 img_unsigned.close()
706 img_keyblock.close()
707
David Zeuthen8fecb282017-12-01 16:24:01 -0500708 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800709 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700710 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500711 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400712 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700713 "--partition_size", str(part_size), "--partition_name",
714 partition_name]
715 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500716 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400717 if args and args.strip():
718 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700719 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500720
721 img.seek(os.SEEK_SET, 0)
722 data = img.read()
723
724 if has_ramdisk:
725 ramdisk_img.close()
726 img.close()
727
728 return data
729
730
Doug Zongkerd5131602012-08-02 14:46:42 -0700731def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800732 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700733 """Return a File object with the desired bootable image.
734
735 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
736 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
737 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700738
Doug Zongker55d93282011-01-25 17:03:34 -0800739 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
740 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700741 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800742 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700743
744 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
745 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700746 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700747 return File.FromLocalFile(name, prebuilt_path)
748
Tao Bao32fcdab2018-10-12 10:30:39 -0700749 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700750
751 if info_dict is None:
752 info_dict = OPTIONS.info_dict
753
754 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800755 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
756 # for recovery.
757 has_ramdisk = (info_dict.get("system_root_image") != "true" or
758 prebuilt_name != "boot.img" or
759 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700760
Doug Zongker6f1d0312014-08-22 08:07:12 -0700761 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400762 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
763 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800764 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700765 if data:
766 return File(name, data)
767 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800768
Doug Zongkereef39442009-04-02 12:14:19 -0700769
Narayan Kamatha07bf042017-08-14 14:49:21 +0100770def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800771 """Gunzips the given gzip compressed file to a given output file."""
772 with gzip.open(in_filename, "rb") as in_file, \
773 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100774 shutil.copyfileobj(in_file, out_file)
775
776
Tao Bao0ff15de2019-03-20 11:26:06 -0700777def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800778 """Unzips the archive to the given directory.
779
780 Args:
781 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800782 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700783 patterns: Files to unzip from the archive. If omitted, will unzip the entire
784 archvie. Non-matching patterns will be filtered out. If there's no match
785 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800786 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800787 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700788 if patterns is not None:
789 # Filter out non-matching patterns. unzip will complain otherwise.
790 with zipfile.ZipFile(filename) as input_zip:
791 names = input_zip.namelist()
792 filtered = [
793 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
794
795 # There isn't any matching files. Don't unzip anything.
796 if not filtered:
797 return
798 cmd.extend(filtered)
799
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800800 RunAndCheckOutput(cmd)
801
802
Doug Zongker75f17362009-12-08 13:46:44 -0800803def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800804 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800805
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800806 Args:
807 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
808 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
809
810 pattern: Files to unzip from the archive. If omitted, will unzip the entire
811 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800812
Tao Bao1c830bf2017-12-25 10:43:47 -0800813 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800814 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800815 """
Doug Zongkereef39442009-04-02 12:14:19 -0700816
Tao Bao1c830bf2017-12-25 10:43:47 -0800817 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800818 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
819 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800820 UnzipToDir(m.group(1), tmp, pattern)
821 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800822 filename = m.group(1)
823 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800824 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800825
Tao Baodba59ee2018-01-09 13:21:02 -0800826 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700827
828
Yifan Hong8a66a712019-04-04 15:37:57 -0700829def GetUserImage(which, tmpdir, input_zip,
830 info_dict=None,
831 allow_shared_blocks=None,
832 hashtree_info_generator=None,
833 reset_file_map=False):
834 """Returns an Image object suitable for passing to BlockImageDiff.
835
836 This function loads the specified image from the given path. If the specified
837 image is sparse, it also performs additional processing for OTA purpose. For
838 example, it always adds block 0 to clobbered blocks list. It also detects
839 files that cannot be reconstructed from the block list, for whom we should
840 avoid applying imgdiff.
841
842 Args:
843 which: The partition name.
844 tmpdir: The directory that contains the prebuilt image and block map file.
845 input_zip: The target-files ZIP archive.
846 info_dict: The dict to be looked up for relevant info.
847 allow_shared_blocks: If image is sparse, whether having shared blocks is
848 allowed. If none, it is looked up from info_dict.
849 hashtree_info_generator: If present and image is sparse, generates the
850 hashtree_info for this sparse image.
851 reset_file_map: If true and image is sparse, reset file map before returning
852 the image.
853 Returns:
854 A Image object. If it is a sparse image and reset_file_map is False, the
855 image will have file_map info loaded.
856 """
857 if info_dict == None:
858 info_dict = LoadInfoDict(input_zip)
859
860 is_sparse = info_dict.get("extfs_sparse_flag")
861
862 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
863 # shared blocks (i.e. some blocks will show up in multiple files' block
864 # list). We can only allocate such shared blocks to the first "owner", and
865 # disable imgdiff for all later occurrences.
866 if allow_shared_blocks is None:
867 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
868
869 if is_sparse:
870 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
871 hashtree_info_generator)
872 if reset_file_map:
873 img.ResetFileMap()
874 return img
875 else:
876 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
877
878
879def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
880 """Returns a Image object suitable for passing to BlockImageDiff.
881
882 This function loads the specified non-sparse image from the given path.
883
884 Args:
885 which: The partition name.
886 tmpdir: The directory that contains the prebuilt image and block map file.
887 Returns:
888 A Image object.
889 """
890 path = os.path.join(tmpdir, "IMAGES", which + ".img")
891 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
892
893 # The image and map files must have been created prior to calling
894 # ota_from_target_files.py (since LMP).
895 assert os.path.exists(path) and os.path.exists(mappath)
896
897 return blockimgdiff.FileImage(path, hashtree_info_generator=
898 hashtree_info_generator)
899
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700900def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
901 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800902 """Returns a SparseImage object suitable for passing to BlockImageDiff.
903
904 This function loads the specified sparse image from the given path, and
905 performs additional processing for OTA purpose. For example, it always adds
906 block 0 to clobbered blocks list. It also detects files that cannot be
907 reconstructed from the block list, for whom we should avoid applying imgdiff.
908
909 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700910 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800911 tmpdir: The directory that contains the prebuilt image and block map file.
912 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800913 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700914 hashtree_info_generator: If present, generates the hashtree_info for this
915 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800916 Returns:
917 A SparseImage object, with file_map info loaded.
918 """
Tao Baoc765cca2018-01-31 17:32:40 -0800919 path = os.path.join(tmpdir, "IMAGES", which + ".img")
920 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
921
922 # The image and map files must have been created prior to calling
923 # ota_from_target_files.py (since LMP).
924 assert os.path.exists(path) and os.path.exists(mappath)
925
926 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
927 # it to clobbered_blocks so that it will be written to the target
928 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
929 clobbered_blocks = "0"
930
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700931 image = sparse_img.SparseImage(
932 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
933 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800934
935 # block.map may contain less blocks, because mke2fs may skip allocating blocks
936 # if they contain all zeros. We can't reconstruct such a file from its block
937 # list. Tag such entries accordingly. (Bug: 65213616)
938 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800939 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700940 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800941 continue
942
Tom Cherryd14b8952018-08-09 14:26:00 -0700943 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
944 # filename listed in system.map may contain an additional leading slash
945 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
946 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700947 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
948
Tom Cherryd14b8952018-08-09 14:26:00 -0700949 # Special handling another case, where files not under /system
950 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700951 if which == 'system' and not arcname.startswith('SYSTEM'):
952 arcname = 'ROOT/' + arcname
953
954 assert arcname in input_zip.namelist(), \
955 "Failed to find the ZIP entry for {}".format(entry)
956
Tao Baoc765cca2018-01-31 17:32:40 -0800957 info = input_zip.getinfo(arcname)
958 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800959
960 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800961 # image, check the original block list to determine its completeness. Note
962 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800963 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800964 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800965
Tao Baoc765cca2018-01-31 17:32:40 -0800966 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
967 ranges.extra['incomplete'] = True
968
969 return image
970
971
Doug Zongkereef39442009-04-02 12:14:19 -0700972def GetKeyPasswords(keylist):
973 """Given a list of keys, prompt the user to enter passwords for
974 those which require them. Return a {key: password} dict. password
975 will be None if the key has no password."""
976
Doug Zongker8ce7c252009-05-22 13:34:54 -0700977 no_passwords = []
978 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700979 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700980 devnull = open("/dev/null", "w+b")
981 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800982 # We don't need a password for things that aren't really keys.
983 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700984 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700985 continue
986
T.R. Fullhart37e10522013-03-18 10:31:26 -0700987 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700988 "-inform", "DER", "-nocrypt"],
989 stdin=devnull.fileno(),
990 stdout=devnull.fileno(),
991 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700992 p.communicate()
993 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700994 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700995 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700996 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700997 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
998 "-inform", "DER", "-passin", "pass:"],
999 stdin=devnull.fileno(),
1000 stdout=devnull.fileno(),
1001 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001002 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001003 if p.returncode == 0:
1004 # Encrypted key with empty string as password.
1005 key_passwords[k] = ''
1006 elif stderr.startswith('Error decrypting key'):
1007 # Definitely encrypted key.
1008 # It would have said "Error reading key" if it didn't parse correctly.
1009 need_passwords.append(k)
1010 else:
1011 # Potentially, a type of key that openssl doesn't understand.
1012 # We'll let the routines in signapk.jar handle it.
1013 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001014 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001015
T.R. Fullhart37e10522013-03-18 10:31:26 -07001016 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001017 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001018 return key_passwords
1019
1020
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001021def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001022 """Gets the minSdkVersion declared in the APK.
1023
1024 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1025 This can be both a decimal number (API Level) or a codename.
1026
1027 Args:
1028 apk_name: The APK filename.
1029
1030 Returns:
1031 The parsed SDK version string.
1032
1033 Raises:
1034 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001035 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001036 proc = Run(
1037 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1038 stderr=subprocess.PIPE)
1039 stdoutdata, stderrdata = proc.communicate()
1040 if proc.returncode != 0:
1041 raise ExternalError(
1042 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1043 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001044
Tao Baof47bf0f2018-03-21 23:28:51 -07001045 for line in stdoutdata.split("\n"):
1046 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001047 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1048 if m:
1049 return m.group(1)
1050 raise ExternalError("No minSdkVersion returned by aapt")
1051
1052
1053def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001054 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001055
Tao Baof47bf0f2018-03-21 23:28:51 -07001056 If minSdkVersion is set to a codename, it is translated to a number using the
1057 provided map.
1058
1059 Args:
1060 apk_name: The APK filename.
1061
1062 Returns:
1063 The parsed SDK version number.
1064
1065 Raises:
1066 ExternalError: On failing to get the min SDK version number.
1067 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001068 version = GetMinSdkVersion(apk_name)
1069 try:
1070 return int(version)
1071 except ValueError:
1072 # Not a decimal number. Codename?
1073 if version in codename_to_api_level_map:
1074 return codename_to_api_level_map[version]
1075 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001076 raise ExternalError(
1077 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1078 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001079
1080
1081def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001082 codename_to_api_level_map=None, whole_file=False,
1083 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001084 """Sign the input_name zip/jar/apk, producing output_name. Use the
1085 given key and password (the latter may be None if the key does not
1086 have a password.
1087
Doug Zongker951495f2009-08-14 12:44:19 -07001088 If whole_file is true, use the "-w" option to SignApk to embed a
1089 signature that covers the whole file in the archive comment of the
1090 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001091
1092 min_api_level is the API Level (int) of the oldest platform this file may end
1093 up on. If not specified for an APK, the API Level is obtained by interpreting
1094 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1095
1096 codename_to_api_level_map is needed to translate the codename which may be
1097 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001098
1099 Caller may optionally specify extra args to be passed to SignApk, which
1100 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001101 """
Tao Bao76def242017-11-21 09:25:31 -08001102 if codename_to_api_level_map is None:
1103 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001104 if extra_signapk_args is None:
1105 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001106
Alex Klyubin9667b182015-12-10 13:38:50 -08001107 java_library_path = os.path.join(
1108 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1109
Tao Baoe95540e2016-11-08 12:08:53 -08001110 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1111 ["-Djava.library.path=" + java_library_path,
1112 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001113 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001114 if whole_file:
1115 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001116
1117 min_sdk_version = min_api_level
1118 if min_sdk_version is None:
1119 if not whole_file:
1120 min_sdk_version = GetMinSdkVersionInt(
1121 input_name, codename_to_api_level_map)
1122 if min_sdk_version is not None:
1123 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1124
T.R. Fullhart37e10522013-03-18 10:31:26 -07001125 cmd.extend([key + OPTIONS.public_key_suffix,
1126 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001127 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001128
Tao Bao73dd4f42018-10-04 16:25:33 -07001129 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001130 if password is not None:
1131 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001132 stdoutdata, _ = proc.communicate(password)
1133 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001134 raise ExternalError(
1135 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001136 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001137
Doug Zongkereef39442009-04-02 12:14:19 -07001138
Doug Zongker37974732010-09-16 17:44:38 -07001139def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001140 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001141
Tao Bao9dd909e2017-11-14 11:27:32 -08001142 For non-AVB images, raise exception if the data is too big. Print a warning
1143 if the data is nearing the maximum size.
1144
1145 For AVB images, the actual image size should be identical to the limit.
1146
1147 Args:
1148 data: A string that contains all the data for the partition.
1149 target: The partition name. The ".img" suffix is optional.
1150 info_dict: The dict to be looked up for relevant info.
1151 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001152 if target.endswith(".img"):
1153 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001154 mount_point = "/" + target
1155
Ying Wangf8824af2014-06-03 14:07:27 -07001156 fs_type = None
1157 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001158 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001159 if mount_point == "/userdata":
1160 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001161 p = info_dict["fstab"][mount_point]
1162 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001163 device = p.device
1164 if "/" in device:
1165 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001166 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001167 if not fs_type or not limit:
1168 return
Doug Zongkereef39442009-04-02 12:14:19 -07001169
Andrew Boie0f9aec82012-02-14 09:32:52 -08001170 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001171 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1172 # path.
1173 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1174 if size != limit:
1175 raise ExternalError(
1176 "Mismatching image size for %s: expected %d actual %d" % (
1177 target, limit, size))
1178 else:
1179 pct = float(size) * 100.0 / limit
1180 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1181 if pct >= 99.0:
1182 raise ExternalError(msg)
1183 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001184 logger.warning("\n WARNING: %s\n", msg)
1185 else:
1186 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001187
1188
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001189def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001190 """Parses the APK certs info from a given target-files zip.
1191
1192 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1193 tuple with the following elements: (1) a dictionary that maps packages to
1194 certs (based on the "certificate" and "private_key" attributes in the file;
1195 (2) a string representing the extension of compressed APKs in the target files
1196 (e.g ".gz", ".bro").
1197
1198 Args:
1199 tf_zip: The input target_files ZipFile (already open).
1200
1201 Returns:
1202 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1203 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1204 no compressed APKs.
1205 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001206 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001207 compressed_extension = None
1208
Tao Bao0f990332017-09-08 19:02:54 -07001209 # META/apkcerts.txt contains the info for _all_ the packages known at build
1210 # time. Filter out the ones that are not installed.
1211 installed_files = set()
1212 for name in tf_zip.namelist():
1213 basename = os.path.basename(name)
1214 if basename:
1215 installed_files.add(basename)
1216
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001217 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1218 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001219 if not line:
1220 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001221 m = re.match(
1222 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1223 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1224 line)
1225 if not m:
1226 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001227
Tao Bao818ddf52018-01-05 11:17:34 -08001228 matches = m.groupdict()
1229 cert = matches["CERT"]
1230 privkey = matches["PRIVKEY"]
1231 name = matches["NAME"]
1232 this_compressed_extension = matches["COMPRESSED"]
1233
1234 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1235 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1236 if cert in SPECIAL_CERT_STRINGS and not privkey:
1237 certmap[name] = cert
1238 elif (cert.endswith(OPTIONS.public_key_suffix) and
1239 privkey.endswith(OPTIONS.private_key_suffix) and
1240 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1241 certmap[name] = cert[:-public_key_suffix_len]
1242 else:
1243 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1244
1245 if not this_compressed_extension:
1246 continue
1247
1248 # Only count the installed files.
1249 filename = name + '.' + this_compressed_extension
1250 if filename not in installed_files:
1251 continue
1252
1253 # Make sure that all the values in the compression map have the same
1254 # extension. We don't support multiple compression methods in the same
1255 # system image.
1256 if compressed_extension:
1257 if this_compressed_extension != compressed_extension:
1258 raise ValueError(
1259 "Multiple compressed extensions: {} vs {}".format(
1260 compressed_extension, this_compressed_extension))
1261 else:
1262 compressed_extension = this_compressed_extension
1263
1264 return (certmap,
1265 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001266
1267
Doug Zongkereef39442009-04-02 12:14:19 -07001268COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001269Global options
1270
1271 -p (--path) <dir>
1272 Prepend <dir>/bin to the list of places to search for binaries run by this
1273 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001274
Doug Zongker05d3dea2009-06-22 11:32:31 -07001275 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001276 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001277
Tao Bao30df8b42018-04-23 15:32:53 -07001278 -x (--extra) <key=value>
1279 Add a key/value pair to the 'extras' dict, which device-specific extension
1280 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001281
Doug Zongkereef39442009-04-02 12:14:19 -07001282 -v (--verbose)
1283 Show command lines being executed.
1284
1285 -h (--help)
1286 Display this usage message and exit.
1287"""
1288
1289def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001290 print(docstring.rstrip("\n"))
1291 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001292
1293
1294def ParseOptions(argv,
1295 docstring,
1296 extra_opts="", extra_long_opts=(),
1297 extra_option_handler=None):
1298 """Parse the options in argv and return any arguments that aren't
1299 flags. docstring is the calling module's docstring, to be displayed
1300 for errors and -h. extra_opts and extra_long_opts are for flags
1301 defined by the caller, which are processed by passing them to
1302 extra_option_handler."""
1303
1304 try:
1305 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001306 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001307 ["help", "verbose", "path=", "signapk_path=",
1308 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001309 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001310 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1311 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001312 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001313 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001314 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001315 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001316 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001317 sys.exit(2)
1318
Doug Zongkereef39442009-04-02 12:14:19 -07001319 for o, a in opts:
1320 if o in ("-h", "--help"):
1321 Usage(docstring)
1322 sys.exit()
1323 elif o in ("-v", "--verbose"):
1324 OPTIONS.verbose = True
1325 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001326 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001327 elif o in ("--signapk_path",):
1328 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001329 elif o in ("--signapk_shared_library_path",):
1330 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001331 elif o in ("--extra_signapk_args",):
1332 OPTIONS.extra_signapk_args = shlex.split(a)
1333 elif o in ("--java_path",):
1334 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001335 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001336 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001337 elif o in ("--public_key_suffix",):
1338 OPTIONS.public_key_suffix = a
1339 elif o in ("--private_key_suffix",):
1340 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001341 elif o in ("--boot_signer_path",):
1342 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001343 elif o in ("--boot_signer_args",):
1344 OPTIONS.boot_signer_args = shlex.split(a)
1345 elif o in ("--verity_signer_path",):
1346 OPTIONS.verity_signer_path = a
1347 elif o in ("--verity_signer_args",):
1348 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001349 elif o in ("-s", "--device_specific"):
1350 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001351 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001352 key, value = a.split("=", 1)
1353 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001354 else:
1355 if extra_option_handler is None or not extra_option_handler(o, a):
1356 assert False, "unknown option \"%s\"" % (o,)
1357
Doug Zongker85448772014-09-09 14:59:20 -07001358 if OPTIONS.search_path:
1359 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1360 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001361
1362 return args
1363
1364
Tao Bao4c851b12016-09-19 13:54:38 -07001365def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001366 """Make a temp file and add it to the list of things to be deleted
1367 when Cleanup() is called. Return the filename."""
1368 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1369 os.close(fd)
1370 OPTIONS.tempfiles.append(fn)
1371 return fn
1372
1373
Tao Bao1c830bf2017-12-25 10:43:47 -08001374def MakeTempDir(prefix='tmp', suffix=''):
1375 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1376
1377 Returns:
1378 The absolute pathname of the new directory.
1379 """
1380 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1381 OPTIONS.tempfiles.append(dir_name)
1382 return dir_name
1383
1384
Doug Zongkereef39442009-04-02 12:14:19 -07001385def Cleanup():
1386 for i in OPTIONS.tempfiles:
1387 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001388 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001389 else:
1390 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001391 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001392
1393
1394class PasswordManager(object):
1395 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001396 self.editor = os.getenv("EDITOR")
1397 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001398
1399 def GetPasswords(self, items):
1400 """Get passwords corresponding to each string in 'items',
1401 returning a dict. (The dict may have keys in addition to the
1402 values in 'items'.)
1403
1404 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1405 user edit that file to add more needed passwords. If no editor is
1406 available, or $ANDROID_PW_FILE isn't define, prompts the user
1407 interactively in the ordinary way.
1408 """
1409
1410 current = self.ReadFile()
1411
1412 first = True
1413 while True:
1414 missing = []
1415 for i in items:
1416 if i not in current or not current[i]:
1417 missing.append(i)
1418 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001419 if not missing:
1420 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001421
1422 for i in missing:
1423 current[i] = ""
1424
1425 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001426 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001427 answer = raw_input("try to edit again? [y]> ").strip()
1428 if answer and answer[0] not in 'yY':
1429 raise RuntimeError("key passwords unavailable")
1430 first = False
1431
1432 current = self.UpdateAndReadFile(current)
1433
Dan Albert8b72aef2015-03-23 19:13:21 -07001434 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001435 """Prompt the user to enter a value (password) for each key in
1436 'current' whose value is fales. Returns a new dict with all the
1437 values.
1438 """
1439 result = {}
1440 for k, v in sorted(current.iteritems()):
1441 if v:
1442 result[k] = v
1443 else:
1444 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001445 result[k] = getpass.getpass(
1446 "Enter password for %s key> " % k).strip()
1447 if result[k]:
1448 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001449 return result
1450
1451 def UpdateAndReadFile(self, current):
1452 if not self.editor or not self.pwfile:
1453 return self.PromptResult(current)
1454
1455 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001456 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001457 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1458 f.write("# (Additional spaces are harmless.)\n\n")
1459
1460 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001461 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1462 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001463 f.write("[[[ %s ]]] %s\n" % (v, k))
1464 if not v and first_line is None:
1465 # position cursor on first line with no password.
1466 first_line = i + 4
1467 f.close()
1468
Tao Bao986ee862018-10-04 15:46:16 -07001469 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001470
1471 return self.ReadFile()
1472
1473 def ReadFile(self):
1474 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001475 if self.pwfile is None:
1476 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001477 try:
1478 f = open(self.pwfile, "r")
1479 for line in f:
1480 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001481 if not line or line[0] == '#':
1482 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001483 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1484 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001485 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001486 else:
1487 result[m.group(2)] = m.group(1)
1488 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001489 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001490 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001491 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001492 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001493
1494
Dan Albert8e0178d2015-01-27 15:53:15 -08001495def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1496 compress_type=None):
1497 import datetime
1498
1499 # http://b/18015246
1500 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1501 # for files larger than 2GiB. We can work around this by adjusting their
1502 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1503 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1504 # it isn't clear to me exactly what circumstances cause this).
1505 # `zipfile.write()` must be used directly to work around this.
1506 #
1507 # This mess can be avoided if we port to python3.
1508 saved_zip64_limit = zipfile.ZIP64_LIMIT
1509 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1510
1511 if compress_type is None:
1512 compress_type = zip_file.compression
1513 if arcname is None:
1514 arcname = filename
1515
1516 saved_stat = os.stat(filename)
1517
1518 try:
1519 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1520 # file to be zipped and reset it when we're done.
1521 os.chmod(filename, perms)
1522
1523 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001524 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1525 # intentional. zip stores datetimes in local time without a time zone
1526 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1527 # in the zip archive.
1528 local_epoch = datetime.datetime.fromtimestamp(0)
1529 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001530 os.utime(filename, (timestamp, timestamp))
1531
1532 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1533 finally:
1534 os.chmod(filename, saved_stat.st_mode)
1535 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1536 zipfile.ZIP64_LIMIT = saved_zip64_limit
1537
1538
Tao Bao58c1b962015-05-20 09:32:18 -07001539def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001540 compress_type=None):
1541 """Wrap zipfile.writestr() function to work around the zip64 limit.
1542
1543 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1544 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1545 when calling crc32(bytes).
1546
1547 But it still works fine to write a shorter string into a large zip file.
1548 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1549 when we know the string won't be too long.
1550 """
1551
1552 saved_zip64_limit = zipfile.ZIP64_LIMIT
1553 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1554
1555 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1556 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001557 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001558 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001559 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001560 else:
Tao Baof3282b42015-04-01 11:21:55 -07001561 zinfo = zinfo_or_arcname
1562
1563 # If compress_type is given, it overrides the value in zinfo.
1564 if compress_type is not None:
1565 zinfo.compress_type = compress_type
1566
Tao Bao58c1b962015-05-20 09:32:18 -07001567 # If perms is given, it has a priority.
1568 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001569 # If perms doesn't set the file type, mark it as a regular file.
1570 if perms & 0o770000 == 0:
1571 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001572 zinfo.external_attr = perms << 16
1573
Tao Baof3282b42015-04-01 11:21:55 -07001574 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001575 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1576
Dan Albert8b72aef2015-03-23 19:13:21 -07001577 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001578 zipfile.ZIP64_LIMIT = saved_zip64_limit
1579
1580
Tao Bao89d7ab22017-12-14 17:05:33 -08001581def ZipDelete(zip_filename, entries):
1582 """Deletes entries from a ZIP file.
1583
1584 Since deleting entries from a ZIP file is not supported, it shells out to
1585 'zip -d'.
1586
1587 Args:
1588 zip_filename: The name of the ZIP file.
1589 entries: The name of the entry, or the list of names to be deleted.
1590
1591 Raises:
1592 AssertionError: In case of non-zero return from 'zip'.
1593 """
1594 if isinstance(entries, basestring):
1595 entries = [entries]
1596 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001597 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001598
1599
Tao Baof3282b42015-04-01 11:21:55 -07001600def ZipClose(zip_file):
1601 # http://b/18015246
1602 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1603 # central directory.
1604 saved_zip64_limit = zipfile.ZIP64_LIMIT
1605 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1606
1607 zip_file.close()
1608
1609 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001610
1611
1612class DeviceSpecificParams(object):
1613 module = None
1614 def __init__(self, **kwargs):
1615 """Keyword arguments to the constructor become attributes of this
1616 object, which is passed to all functions in the device-specific
1617 module."""
1618 for k, v in kwargs.iteritems():
1619 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001620 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001621
1622 if self.module is None:
1623 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001624 if not path:
1625 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001626 try:
1627 if os.path.isdir(path):
1628 info = imp.find_module("releasetools", [path])
1629 else:
1630 d, f = os.path.split(path)
1631 b, x = os.path.splitext(f)
1632 if x == ".py":
1633 f = b
1634 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001635 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001636 self.module = imp.load_module("device_specific", *info)
1637 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001638 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001639
1640 def _DoCall(self, function_name, *args, **kwargs):
1641 """Call the named function in the device-specific module, passing
1642 the given args and kwargs. The first argument to the call will be
1643 the DeviceSpecific object itself. If there is no module, or the
1644 module does not define the function, return the value of the
1645 'default' kwarg (which itself defaults to None)."""
1646 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001647 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001648 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1649
1650 def FullOTA_Assertions(self):
1651 """Called after emitting the block of assertions at the top of a
1652 full OTA package. Implementations can add whatever additional
1653 assertions they like."""
1654 return self._DoCall("FullOTA_Assertions")
1655
Doug Zongkere5ff5902012-01-17 10:55:37 -08001656 def FullOTA_InstallBegin(self):
1657 """Called at the start of full OTA installation."""
1658 return self._DoCall("FullOTA_InstallBegin")
1659
Yifan Hong10c530d2018-12-27 17:34:18 -08001660 def FullOTA_GetBlockDifferences(self):
1661 """Called during full OTA installation and verification.
1662 Implementation should return a list of BlockDifference objects describing
1663 the update on each additional partitions.
1664 """
1665 return self._DoCall("FullOTA_GetBlockDifferences")
1666
Doug Zongker05d3dea2009-06-22 11:32:31 -07001667 def FullOTA_InstallEnd(self):
1668 """Called at the end of full OTA installation; typically this is
1669 used to install the image for the device's baseband processor."""
1670 return self._DoCall("FullOTA_InstallEnd")
1671
1672 def IncrementalOTA_Assertions(self):
1673 """Called after emitting the block of assertions at the top of an
1674 incremental OTA package. Implementations can add whatever
1675 additional assertions they like."""
1676 return self._DoCall("IncrementalOTA_Assertions")
1677
Doug Zongkere5ff5902012-01-17 10:55:37 -08001678 def IncrementalOTA_VerifyBegin(self):
1679 """Called at the start of the verification phase of incremental
1680 OTA installation; additional checks can be placed here to abort
1681 the script before any changes are made."""
1682 return self._DoCall("IncrementalOTA_VerifyBegin")
1683
Doug Zongker05d3dea2009-06-22 11:32:31 -07001684 def IncrementalOTA_VerifyEnd(self):
1685 """Called at the end of the verification phase of incremental OTA
1686 installation; additional checks can be placed here to abort the
1687 script before any changes are made."""
1688 return self._DoCall("IncrementalOTA_VerifyEnd")
1689
Doug Zongkere5ff5902012-01-17 10:55:37 -08001690 def IncrementalOTA_InstallBegin(self):
1691 """Called at the start of incremental OTA installation (after
1692 verification is complete)."""
1693 return self._DoCall("IncrementalOTA_InstallBegin")
1694
Yifan Hong10c530d2018-12-27 17:34:18 -08001695 def IncrementalOTA_GetBlockDifferences(self):
1696 """Called during incremental OTA installation and verification.
1697 Implementation should return a list of BlockDifference objects describing
1698 the update on each additional partitions.
1699 """
1700 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1701
Doug Zongker05d3dea2009-06-22 11:32:31 -07001702 def IncrementalOTA_InstallEnd(self):
1703 """Called at the end of incremental OTA installation; typically
1704 this is used to install the image for the device's baseband
1705 processor."""
1706 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001707
Tao Bao9bc6bb22015-11-09 16:58:28 -08001708 def VerifyOTA_Assertions(self):
1709 return self._DoCall("VerifyOTA_Assertions")
1710
Tao Bao76def242017-11-21 09:25:31 -08001711
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001712class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001713 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001714 self.name = name
1715 self.data = data
1716 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001717 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001718 self.sha1 = sha1(data).hexdigest()
1719
1720 @classmethod
1721 def FromLocalFile(cls, name, diskname):
1722 f = open(diskname, "rb")
1723 data = f.read()
1724 f.close()
1725 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001726
1727 def WriteToTemp(self):
1728 t = tempfile.NamedTemporaryFile()
1729 t.write(self.data)
1730 t.flush()
1731 return t
1732
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001733 def WriteToDir(self, d):
1734 with open(os.path.join(d, self.name), "wb") as fp:
1735 fp.write(self.data)
1736
Geremy Condra36bd3652014-02-06 19:45:10 -08001737 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001738 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001739
Tao Bao76def242017-11-21 09:25:31 -08001740
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001741DIFF_PROGRAM_BY_EXT = {
1742 ".gz" : "imgdiff",
1743 ".zip" : ["imgdiff", "-z"],
1744 ".jar" : ["imgdiff", "-z"],
1745 ".apk" : ["imgdiff", "-z"],
1746 ".img" : "imgdiff",
1747 }
1748
Tao Bao76def242017-11-21 09:25:31 -08001749
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001750class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001751 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001752 self.tf = tf
1753 self.sf = sf
1754 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001755 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001756
1757 def ComputePatch(self):
1758 """Compute the patch (as a string of data) needed to turn sf into
1759 tf. Returns the same tuple as GetPatch()."""
1760
1761 tf = self.tf
1762 sf = self.sf
1763
Doug Zongker24cd2802012-08-14 16:36:15 -07001764 if self.diff_program:
1765 diff_program = self.diff_program
1766 else:
1767 ext = os.path.splitext(tf.name)[1]
1768 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001769
1770 ttemp = tf.WriteToTemp()
1771 stemp = sf.WriteToTemp()
1772
1773 ext = os.path.splitext(tf.name)[1]
1774
1775 try:
1776 ptemp = tempfile.NamedTemporaryFile()
1777 if isinstance(diff_program, list):
1778 cmd = copy.copy(diff_program)
1779 else:
1780 cmd = [diff_program]
1781 cmd.append(stemp.name)
1782 cmd.append(ttemp.name)
1783 cmd.append(ptemp.name)
1784 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001785 err = []
1786 def run():
1787 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001788 if e:
1789 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001790 th = threading.Thread(target=run)
1791 th.start()
1792 th.join(timeout=300) # 5 mins
1793 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001794 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001795 p.terminate()
1796 th.join(5)
1797 if th.is_alive():
1798 p.kill()
1799 th.join()
1800
Tianjie Xua2a9f992018-01-05 15:15:54 -08001801 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001802 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001803 self.patch = None
1804 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001805 diff = ptemp.read()
1806 finally:
1807 ptemp.close()
1808 stemp.close()
1809 ttemp.close()
1810
1811 self.patch = diff
1812 return self.tf, self.sf, self.patch
1813
1814
1815 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001816 """Returns a tuple of (target_file, source_file, patch_data).
1817
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001818 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001819 computing the patch failed.
1820 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001821 return self.tf, self.sf, self.patch
1822
1823
1824def ComputeDifferences(diffs):
1825 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001826 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001827
1828 # Do the largest files first, to try and reduce the long-pole effect.
1829 by_size = [(i.tf.size, i) for i in diffs]
1830 by_size.sort(reverse=True)
1831 by_size = [i[1] for i in by_size]
1832
1833 lock = threading.Lock()
1834 diff_iter = iter(by_size) # accessed under lock
1835
1836 def worker():
1837 try:
1838 lock.acquire()
1839 for d in diff_iter:
1840 lock.release()
1841 start = time.time()
1842 d.ComputePatch()
1843 dur = time.time() - start
1844 lock.acquire()
1845
1846 tf, sf, patch = d.GetPatch()
1847 if sf.name == tf.name:
1848 name = tf.name
1849 else:
1850 name = "%s (%s)" % (tf.name, sf.name)
1851 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001852 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001853 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001854 logger.info(
1855 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1856 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001857 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001858 except Exception:
1859 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001860 raise
1861
1862 # start worker threads; wait for them all to finish.
1863 threads = [threading.Thread(target=worker)
1864 for i in range(OPTIONS.worker_threads)]
1865 for th in threads:
1866 th.start()
1867 while threads:
1868 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001869
1870
Dan Albert8b72aef2015-03-23 19:13:21 -07001871class BlockDifference(object):
1872 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001873 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001874 self.tgt = tgt
1875 self.src = src
1876 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001877 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001878 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001879
Tao Baodd2a5892015-03-12 12:32:37 -07001880 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001881 version = max(
1882 int(i) for i in
1883 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001884 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001885 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001886
1887 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001888 version=self.version,
1889 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001890 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001891 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001892 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001893 self.touched_src_ranges = b.touched_src_ranges
1894 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001895
Yifan Hong10c530d2018-12-27 17:34:18 -08001896 # On devices with dynamic partitions, for new partitions,
1897 # src is None but OPTIONS.source_info_dict is not.
1898 if OPTIONS.source_info_dict is None:
1899 is_dynamic_build = OPTIONS.info_dict.get(
1900 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001901 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001902 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001903 is_dynamic_build = OPTIONS.source_info_dict.get(
1904 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001905 is_dynamic_source = partition in shlex.split(
1906 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001907
Yifan Hongbb2658d2019-01-25 12:30:58 -08001908 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001909 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1910
Yifan Hongbb2658d2019-01-25 12:30:58 -08001911 # For dynamic partitions builds, check partition list in both source
1912 # and target build because new partitions may be added, and existing
1913 # partitions may be removed.
1914 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1915
Yifan Hong10c530d2018-12-27 17:34:18 -08001916 if is_dynamic:
1917 self.device = 'map_partition("%s")' % partition
1918 else:
1919 if OPTIONS.source_info_dict is None:
1920 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1921 else:
1922 _, device_path = GetTypeAndDevice("/" + partition,
1923 OPTIONS.source_info_dict)
1924 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001925
Tao Baod8d14be2016-02-04 14:26:02 -08001926 @property
1927 def required_cache(self):
1928 return self._required_cache
1929
Tao Bao76def242017-11-21 09:25:31 -08001930 def WriteScript(self, script, output_zip, progress=None,
1931 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001932 if not self.src:
1933 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001934 script.Print("Patching %s image unconditionally..." % (self.partition,))
1935 else:
1936 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001937
Dan Albert8b72aef2015-03-23 19:13:21 -07001938 if progress:
1939 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001940 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001941
1942 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001943 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001944
Tao Bao9bc6bb22015-11-09 16:58:28 -08001945 def WriteStrictVerifyScript(self, script):
1946 """Verify all the blocks in the care_map, including clobbered blocks.
1947
1948 This differs from the WriteVerifyScript() function: a) it prints different
1949 error messages; b) it doesn't allow half-way updated images to pass the
1950 verification."""
1951
1952 partition = self.partition
1953 script.Print("Verifying %s..." % (partition,))
1954 ranges = self.tgt.care_map
1955 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001956 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001957 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1958 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001959 self.device, ranges_str,
1960 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001961 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001962 script.AppendExtra("")
1963
Tao Baod522bdc2016-04-12 15:53:16 -07001964 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001965 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001966
1967 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001968 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001969 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001970
1971 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001972 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001973 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001974 ranges = self.touched_src_ranges
1975 expected_sha1 = self.touched_src_sha1
1976 else:
1977 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1978 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001979
1980 # No blocks to be checked, skipping.
1981 if not ranges:
1982 return
1983
Tao Bao5ece99d2015-05-12 11:42:31 -07001984 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001985 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001986 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001987 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1988 '"%s.patch.dat")) then' % (
1989 self.device, ranges_str, expected_sha1,
1990 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001991 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001992 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001993
Tianjie Xufc3422a2015-12-15 11:53:59 -08001994 if self.version >= 4:
1995
1996 # Bug: 21124327
1997 # When generating incrementals for the system and vendor partitions in
1998 # version 4 or newer, explicitly check the first block (which contains
1999 # the superblock) of the partition to see if it's what we expect. If
2000 # this check fails, give an explicit log message about the partition
2001 # having been remounted R/W (the most likely explanation).
2002 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002003 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002004
2005 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002006 if partition == "system":
2007 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2008 else:
2009 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002010 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002011 'ifelse (block_image_recover({device}, "{ranges}") && '
2012 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002013 'package_extract_file("{partition}.transfer.list"), '
2014 '"{partition}.new.dat", "{partition}.patch.dat"), '
2015 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002016 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002017 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002018 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002019
Tao Baodd2a5892015-03-12 12:32:37 -07002020 # Abort the OTA update. Note that the incremental OTA cannot be applied
2021 # even if it may match the checksum of the target partition.
2022 # a) If version < 3, operations like move and erase will make changes
2023 # unconditionally and damage the partition.
2024 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002025 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002026 if partition == "system":
2027 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2028 else:
2029 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2030 script.AppendExtra((
2031 'abort("E%d: %s partition has unexpected contents");\n'
2032 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002033
Yifan Hong10c530d2018-12-27 17:34:18 -08002034 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002035 partition = self.partition
2036 script.Print('Verifying the updated %s image...' % (partition,))
2037 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2038 ranges = self.tgt.care_map
2039 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002040 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002041 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002042 self.device, ranges_str,
2043 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002044
2045 # Bug: 20881595
2046 # Verify that extended blocks are really zeroed out.
2047 if self.tgt.extended:
2048 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002049 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002050 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002051 self.device, ranges_str,
2052 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002053 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002054 if partition == "system":
2055 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2056 else:
2057 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002058 script.AppendExtra(
2059 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002060 ' abort("E%d: %s partition has unexpected non-zero contents after '
2061 'OTA update");\n'
2062 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002063 else:
2064 script.Print('Verified the updated %s image.' % (partition,))
2065
Tianjie Xu209db462016-05-24 17:34:52 -07002066 if partition == "system":
2067 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2068 else:
2069 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2070
Tao Bao5fcaaef2015-06-01 13:40:49 -07002071 script.AppendExtra(
2072 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002073 ' abort("E%d: %s partition has unexpected contents after OTA '
2074 'update");\n'
2075 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002076
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002077 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002078 ZipWrite(output_zip,
2079 '{}.transfer.list'.format(self.path),
2080 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002081
Tao Bao76def242017-11-21 09:25:31 -08002082 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2083 # its size. Quailty 9 almost triples the compression time but doesn't
2084 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002085 # zip | brotli(quality 6) | brotli(quality 9)
2086 # compressed_size: 942M | 869M (~8% reduced) | 854M
2087 # compression_time: 75s | 265s | 719s
2088 # decompression_time: 15s | 25s | 25s
2089
2090 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002091 brotli_cmd = ['brotli', '--quality=6',
2092 '--output={}.new.dat.br'.format(self.path),
2093 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002094 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002095 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002096
2097 new_data_name = '{}.new.dat.br'.format(self.partition)
2098 ZipWrite(output_zip,
2099 '{}.new.dat.br'.format(self.path),
2100 new_data_name,
2101 compress_type=zipfile.ZIP_STORED)
2102 else:
2103 new_data_name = '{}.new.dat'.format(self.partition)
2104 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2105
Dan Albert8e0178d2015-01-27 15:53:15 -08002106 ZipWrite(output_zip,
2107 '{}.patch.dat'.format(self.path),
2108 '{}.patch.dat'.format(self.partition),
2109 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002110
Tianjie Xu209db462016-05-24 17:34:52 -07002111 if self.partition == "system":
2112 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2113 else:
2114 code = ErrorCode.VENDOR_UPDATE_FAILURE
2115
Yifan Hong10c530d2018-12-27 17:34:18 -08002116 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002117 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002118 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002119 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002120 device=self.device, partition=self.partition,
2121 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002122 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002123
Dan Albert8b72aef2015-03-23 19:13:21 -07002124 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002125 data = source.ReadRangeSet(ranges)
2126 ctx = sha1()
2127
2128 for p in data:
2129 ctx.update(p)
2130
2131 return ctx.hexdigest()
2132
Tao Baoe9b61912015-07-09 17:37:49 -07002133 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2134 """Return the hash value for all zero blocks."""
2135 zero_block = '\x00' * 4096
2136 ctx = sha1()
2137 for _ in range(num_blocks):
2138 ctx.update(zero_block)
2139
2140 return ctx.hexdigest()
2141
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002142
2143DataImage = blockimgdiff.DataImage
Yifan Hong8a66a712019-04-04 15:37:57 -07002144EmptyImage = blockimgdiff.EmptyImage
Tao Bao76def242017-11-21 09:25:31 -08002145
Doug Zongker96a57e72010-09-26 14:57:41 -07002146# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002147PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002148 "ext4": "EMMC",
2149 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002150 "f2fs": "EMMC",
2151 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002152}
Doug Zongker96a57e72010-09-26 14:57:41 -07002153
Tao Bao76def242017-11-21 09:25:31 -08002154
Doug Zongker96a57e72010-09-26 14:57:41 -07002155def GetTypeAndDevice(mount_point, info):
2156 fstab = info["fstab"]
2157 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002158 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2159 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002160 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002161 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002162
2163
2164def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002165 """Parses and converts a PEM-encoded certificate into DER-encoded.
2166
2167 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2168
2169 Returns:
2170 The decoded certificate string.
2171 """
2172 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002173 save = False
2174 for line in data.split("\n"):
2175 if "--END CERTIFICATE--" in line:
2176 break
2177 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002178 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002179 if "--BEGIN CERTIFICATE--" in line:
2180 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002181 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002182 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002183
Tao Bao04e1f012018-02-04 12:13:35 -08002184
2185def ExtractPublicKey(cert):
2186 """Extracts the public key (PEM-encoded) from the given certificate file.
2187
2188 Args:
2189 cert: The certificate filename.
2190
2191 Returns:
2192 The public key string.
2193
2194 Raises:
2195 AssertionError: On non-zero return from 'openssl'.
2196 """
2197 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2198 # While openssl 1.1 writes the key into the given filename followed by '-out',
2199 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2200 # stdout instead.
2201 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2202 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2203 pubkey, stderrdata = proc.communicate()
2204 assert proc.returncode == 0, \
2205 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2206 return pubkey
2207
2208
Tao Bao2cc0ca12019-03-15 10:44:43 -07002209def ExtractAvbPublicKey(key):
2210 """Extracts the AVB public key from the given public or private key.
2211
2212 Args:
2213 key: The input key file, which should be PEM-encoded public or private key.
2214
2215 Returns:
2216 The path to the extracted AVB public key file.
2217 """
2218 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2219 RunAndCheckOutput(
2220 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2221 return output
2222
2223
Doug Zongker412c02f2014-02-13 10:58:24 -08002224def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2225 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002226 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002227
Tao Bao6d5d6232018-03-09 17:04:42 -08002228 Most of the space in the boot and recovery images is just the kernel, which is
2229 identical for the two, so the resulting patch should be efficient. Add it to
2230 the output zip, along with a shell script that is run from init.rc on first
2231 boot to actually do the patching and install the new recovery image.
2232
2233 Args:
2234 input_dir: The top-level input directory of the target-files.zip.
2235 output_sink: The callback function that writes the result.
2236 recovery_img: File object for the recovery image.
2237 boot_img: File objects for the boot image.
2238 info_dict: A dict returned by common.LoadInfoDict() on the input
2239 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002240 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002241 if info_dict is None:
2242 info_dict = OPTIONS.info_dict
2243
Tao Bao6d5d6232018-03-09 17:04:42 -08002244 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002245
Tao Baof2cffbd2015-07-22 12:33:18 -07002246 if full_recovery_image:
2247 output_sink("etc/recovery.img", recovery_img.data)
2248
2249 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002250 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002251 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002252 # With system-root-image, boot and recovery images will have mismatching
2253 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2254 # to handle such a case.
2255 if system_root_image:
2256 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002257 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002258 assert not os.path.exists(path)
2259 else:
2260 diff_program = ["imgdiff"]
2261 if os.path.exists(path):
2262 diff_program.append("-b")
2263 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002264 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002265 else:
2266 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002267
2268 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2269 _, _, patch = d.ComputePatch()
2270 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002271
Dan Albertebb19aa2015-03-27 19:11:53 -07002272 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002273 # The following GetTypeAndDevice()s need to use the path in the target
2274 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002275 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2276 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2277 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002278 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002279
Tao Baof2cffbd2015-07-22 12:33:18 -07002280 if full_recovery_image:
2281 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002282if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2283 applypatch \\
2284 --flash /system/etc/recovery.img \\
2285 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2286 log -t recovery "Installing new recovery image: succeeded" || \\
2287 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002288else
2289 log -t recovery "Recovery image already installed"
2290fi
2291""" % {'type': recovery_type,
2292 'device': recovery_device,
2293 'sha1': recovery_img.sha1,
2294 'size': recovery_img.size}
2295 else:
2296 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002297if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2298 applypatch %(bonus_args)s \\
2299 --patch /system/recovery-from-boot.p \\
2300 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2301 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2302 log -t recovery "Installing new recovery image: succeeded" || \\
2303 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002304else
2305 log -t recovery "Recovery image already installed"
2306fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002307""" % {'boot_size': boot_img.size,
2308 'boot_sha1': boot_img.sha1,
2309 'recovery_size': recovery_img.size,
2310 'recovery_sha1': recovery_img.sha1,
2311 'boot_type': boot_type,
2312 'boot_device': boot_device,
2313 'recovery_type': recovery_type,
2314 'recovery_device': recovery_device,
2315 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002316
2317 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002318 # in the L release.
2319 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002320
Tao Bao32fcdab2018-10-12 10:30:39 -07002321 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002322
2323 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002324
2325
2326class DynamicPartitionUpdate(object):
2327 def __init__(self, src_group=None, tgt_group=None, progress=None,
2328 block_difference=None):
2329 self.src_group = src_group
2330 self.tgt_group = tgt_group
2331 self.progress = progress
2332 self.block_difference = block_difference
2333
2334 @property
2335 def src_size(self):
2336 if not self.block_difference:
2337 return 0
2338 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2339
2340 @property
2341 def tgt_size(self):
2342 if not self.block_difference:
2343 return 0
2344 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2345
2346 @staticmethod
2347 def _GetSparseImageSize(img):
2348 if not img:
2349 return 0
2350 return img.blocksize * img.total_blocks
2351
2352
2353class DynamicGroupUpdate(object):
2354 def __init__(self, src_size=None, tgt_size=None):
2355 # None: group does not exist. 0: no size limits.
2356 self.src_size = src_size
2357 self.tgt_size = tgt_size
2358
2359
2360class DynamicPartitionsDifference(object):
2361 def __init__(self, info_dict, block_diffs, progress_dict=None,
2362 source_info_dict=None):
2363 if progress_dict is None:
2364 progress_dict = dict()
2365
2366 self._remove_all_before_apply = False
2367 if source_info_dict is None:
2368 self._remove_all_before_apply = True
2369 source_info_dict = dict()
2370
2371 block_diff_dict = {e.partition:e for e in block_diffs}
2372 assert len(block_diff_dict) == len(block_diffs), \
2373 "Duplicated BlockDifference object for {}".format(
2374 [partition for partition, count in
2375 collections.Counter(e.partition for e in block_diffs).items()
2376 if count > 1])
2377
Yifan Hong79997e52019-01-23 16:56:19 -08002378 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002379
2380 for p, block_diff in block_diff_dict.items():
2381 self._partition_updates[p] = DynamicPartitionUpdate()
2382 self._partition_updates[p].block_difference = block_diff
2383
2384 for p, progress in progress_dict.items():
2385 if p in self._partition_updates:
2386 self._partition_updates[p].progress = progress
2387
2388 tgt_groups = shlex.split(info_dict.get(
2389 "super_partition_groups", "").strip())
2390 src_groups = shlex.split(source_info_dict.get(
2391 "super_partition_groups", "").strip())
2392
2393 for g in tgt_groups:
2394 for p in shlex.split(info_dict.get(
2395 "super_%s_partition_list" % g, "").strip()):
2396 assert p in self._partition_updates, \
2397 "{} is in target super_{}_partition_list but no BlockDifference " \
2398 "object is provided.".format(p, g)
2399 self._partition_updates[p].tgt_group = g
2400
2401 for g in src_groups:
2402 for p in shlex.split(source_info_dict.get(
2403 "super_%s_partition_list" % g, "").strip()):
2404 assert p in self._partition_updates, \
2405 "{} is in source super_{}_partition_list but no BlockDifference " \
2406 "object is provided.".format(p, g)
2407 self._partition_updates[p].src_group = g
2408
Yifan Hong45433e42019-01-18 13:55:25 -08002409 target_dynamic_partitions = set(shlex.split(info_dict.get(
2410 "dynamic_partition_list", "").strip()))
2411 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2412 if u.tgt_size)
2413 assert block_diffs_with_target == target_dynamic_partitions, \
2414 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2415 list(target_dynamic_partitions), list(block_diffs_with_target))
2416
2417 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2418 "dynamic_partition_list", "").strip()))
2419 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2420 if u.src_size)
2421 assert block_diffs_with_source == source_dynamic_partitions, \
2422 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2423 list(source_dynamic_partitions), list(block_diffs_with_source))
2424
Yifan Hong10c530d2018-12-27 17:34:18 -08002425 if self._partition_updates:
2426 logger.info("Updating dynamic partitions %s",
2427 self._partition_updates.keys())
2428
Yifan Hong79997e52019-01-23 16:56:19 -08002429 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002430
2431 for g in tgt_groups:
2432 self._group_updates[g] = DynamicGroupUpdate()
2433 self._group_updates[g].tgt_size = int(info_dict.get(
2434 "super_%s_group_size" % g, "0").strip())
2435
2436 for g in src_groups:
2437 if g not in self._group_updates:
2438 self._group_updates[g] = DynamicGroupUpdate()
2439 self._group_updates[g].src_size = int(source_info_dict.get(
2440 "super_%s_group_size" % g, "0").strip())
2441
2442 self._Compute()
2443
2444 def WriteScript(self, script, output_zip, write_verify_script=False):
2445 script.Comment('--- Start patching dynamic partitions ---')
2446 for p, u in self._partition_updates.items():
2447 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2448 script.Comment('Patch partition %s' % p)
2449 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2450 write_verify_script=False)
2451
2452 op_list_path = MakeTempFile()
2453 with open(op_list_path, 'w') as f:
2454 for line in self._op_list:
2455 f.write('{}\n'.format(line))
2456
2457 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2458
2459 script.Comment('Update dynamic partition metadata')
2460 script.AppendExtra('assert(update_dynamic_partitions('
2461 'package_extract_file("dynamic_partitions_op_list")));')
2462
2463 if write_verify_script:
2464 for p, u in self._partition_updates.items():
2465 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2466 u.block_difference.WritePostInstallVerifyScript(script)
2467 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2468
2469 for p, u in self._partition_updates.items():
2470 if u.tgt_size and u.src_size <= u.tgt_size:
2471 script.Comment('Patch partition %s' % p)
2472 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2473 write_verify_script=write_verify_script)
2474 if write_verify_script:
2475 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2476
2477 script.Comment('--- End patching dynamic partitions ---')
2478
2479 def _Compute(self):
2480 self._op_list = list()
2481
2482 def append(line):
2483 self._op_list.append(line)
2484
2485 def comment(line):
2486 self._op_list.append("# %s" % line)
2487
2488 if self._remove_all_before_apply:
2489 comment('Remove all existing dynamic partitions and groups before '
2490 'applying full OTA')
2491 append('remove_all_groups')
2492
2493 for p, u in self._partition_updates.items():
2494 if u.src_group and not u.tgt_group:
2495 append('remove %s' % p)
2496
2497 for p, u in self._partition_updates.items():
2498 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2499 comment('Move partition %s from %s to default' % (p, u.src_group))
2500 append('move %s default' % p)
2501
2502 for p, u in self._partition_updates.items():
2503 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2504 comment('Shrink partition %s from %d to %d' %
2505 (p, u.src_size, u.tgt_size))
2506 append('resize %s %s' % (p, u.tgt_size))
2507
2508 for g, u in self._group_updates.items():
2509 if u.src_size is not None and u.tgt_size is None:
2510 append('remove_group %s' % g)
2511 if (u.src_size is not None and u.tgt_size is not None and
2512 u.src_size > u.tgt_size):
2513 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2514 append('resize_group %s %d' % (g, u.tgt_size))
2515
2516 for g, u in self._group_updates.items():
2517 if u.src_size is None and u.tgt_size is not None:
2518 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2519 append('add_group %s %d' % (g, u.tgt_size))
2520 if (u.src_size is not None and u.tgt_size is not None and
2521 u.src_size < u.tgt_size):
2522 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2523 append('resize_group %s %d' % (g, u.tgt_size))
2524
2525 for p, u in self._partition_updates.items():
2526 if u.tgt_group and not u.src_group:
2527 comment('Add partition %s to group %s' % (p, u.tgt_group))
2528 append('add %s %s' % (p, u.tgt_group))
2529
2530 for p, u in self._partition_updates.items():
2531 if u.tgt_size and u.src_size < u.tgt_size:
2532 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2533 append('resize %s %d' % (p, u.tgt_size))
2534
2535 for p, u in self._partition_updates.items():
2536 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2537 comment('Move partition %s from default to %s' %
2538 (p, u.tgt_group))
2539 append('move %s %s' % (p, u.tgt_group))