blob: 37811e58be71ad94d8a10f006fff1fadfd733b48 [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:
335 # We carry a copy of file_contexts.bin under META/. If not available, search
336 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
337 # images than the one running on device, in that case, we must have the one
338 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700339 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700340 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700341 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700342
Tom Cherryd14b8952018-08-09 14:26:00 -0700343 d["selinux_fc"] = 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
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700829def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
830 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800831 """Returns a SparseImage object suitable for passing to BlockImageDiff.
832
833 This function loads the specified sparse image from the given path, and
834 performs additional processing for OTA purpose. For example, it always adds
835 block 0 to clobbered blocks list. It also detects files that cannot be
836 reconstructed from the block list, for whom we should avoid applying imgdiff.
837
838 Args:
839 which: The partition name, which must be "system" or "vendor".
840 tmpdir: The directory that contains the prebuilt image and block map file.
841 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800842 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700843 hashtree_info_generator: If present, generates the hashtree_info for this
844 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800845 Returns:
846 A SparseImage object, with file_map info loaded.
847 """
848 assert which in ("system", "vendor")
849
850 path = os.path.join(tmpdir, "IMAGES", which + ".img")
851 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
852
853 # The image and map files must have been created prior to calling
854 # ota_from_target_files.py (since LMP).
855 assert os.path.exists(path) and os.path.exists(mappath)
856
857 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
858 # it to clobbered_blocks so that it will be written to the target
859 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
860 clobbered_blocks = "0"
861
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700862 image = sparse_img.SparseImage(
863 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
864 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800865
866 # block.map may contain less blocks, because mke2fs may skip allocating blocks
867 # if they contain all zeros. We can't reconstruct such a file from its block
868 # list. Tag such entries accordingly. (Bug: 65213616)
869 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800870 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700871 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800872 continue
873
Tom Cherryd14b8952018-08-09 14:26:00 -0700874 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
875 # filename listed in system.map may contain an additional leading slash
876 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
877 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700878 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
879
Tom Cherryd14b8952018-08-09 14:26:00 -0700880 # Special handling another case, where files not under /system
881 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700882 if which == 'system' and not arcname.startswith('SYSTEM'):
883 arcname = 'ROOT/' + arcname
884
885 assert arcname in input_zip.namelist(), \
886 "Failed to find the ZIP entry for {}".format(entry)
887
Tao Baoc765cca2018-01-31 17:32:40 -0800888 info = input_zip.getinfo(arcname)
889 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800890
891 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800892 # image, check the original block list to determine its completeness. Note
893 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800894 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800895 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800896
Tao Baoc765cca2018-01-31 17:32:40 -0800897 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
898 ranges.extra['incomplete'] = True
899
900 return image
901
902
Doug Zongkereef39442009-04-02 12:14:19 -0700903def GetKeyPasswords(keylist):
904 """Given a list of keys, prompt the user to enter passwords for
905 those which require them. Return a {key: password} dict. password
906 will be None if the key has no password."""
907
Doug Zongker8ce7c252009-05-22 13:34:54 -0700908 no_passwords = []
909 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700910 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700911 devnull = open("/dev/null", "w+b")
912 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800913 # We don't need a password for things that aren't really keys.
914 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700915 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700916 continue
917
T.R. Fullhart37e10522013-03-18 10:31:26 -0700918 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700919 "-inform", "DER", "-nocrypt"],
920 stdin=devnull.fileno(),
921 stdout=devnull.fileno(),
922 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700923 p.communicate()
924 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700925 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700926 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700927 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700928 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
929 "-inform", "DER", "-passin", "pass:"],
930 stdin=devnull.fileno(),
931 stdout=devnull.fileno(),
932 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700933 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700934 if p.returncode == 0:
935 # Encrypted key with empty string as password.
936 key_passwords[k] = ''
937 elif stderr.startswith('Error decrypting key'):
938 # Definitely encrypted key.
939 # It would have said "Error reading key" if it didn't parse correctly.
940 need_passwords.append(k)
941 else:
942 # Potentially, a type of key that openssl doesn't understand.
943 # We'll let the routines in signapk.jar handle it.
944 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700945 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700946
T.R. Fullhart37e10522013-03-18 10:31:26 -0700947 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800948 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700949 return key_passwords
950
951
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800952def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700953 """Gets the minSdkVersion declared in the APK.
954
955 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
956 This can be both a decimal number (API Level) or a codename.
957
958 Args:
959 apk_name: The APK filename.
960
961 Returns:
962 The parsed SDK version string.
963
964 Raises:
965 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800966 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700967 proc = Run(
968 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
969 stderr=subprocess.PIPE)
970 stdoutdata, stderrdata = proc.communicate()
971 if proc.returncode != 0:
972 raise ExternalError(
973 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
974 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800975
Tao Baof47bf0f2018-03-21 23:28:51 -0700976 for line in stdoutdata.split("\n"):
977 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800978 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
979 if m:
980 return m.group(1)
981 raise ExternalError("No minSdkVersion returned by aapt")
982
983
984def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700985 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800986
Tao Baof47bf0f2018-03-21 23:28:51 -0700987 If minSdkVersion is set to a codename, it is translated to a number using the
988 provided map.
989
990 Args:
991 apk_name: The APK filename.
992
993 Returns:
994 The parsed SDK version number.
995
996 Raises:
997 ExternalError: On failing to get the min SDK version number.
998 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800999 version = GetMinSdkVersion(apk_name)
1000 try:
1001 return int(version)
1002 except ValueError:
1003 # Not a decimal number. Codename?
1004 if version in codename_to_api_level_map:
1005 return codename_to_api_level_map[version]
1006 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001007 raise ExternalError(
1008 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1009 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001010
1011
1012def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -08001013 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -07001014 """Sign the input_name zip/jar/apk, producing output_name. Use the
1015 given key and password (the latter may be None if the key does not
1016 have a password.
1017
Doug Zongker951495f2009-08-14 12:44:19 -07001018 If whole_file is true, use the "-w" option to SignApk to embed a
1019 signature that covers the whole file in the archive comment of the
1020 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001021
1022 min_api_level is the API Level (int) of the oldest platform this file may end
1023 up on. If not specified for an APK, the API Level is obtained by interpreting
1024 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1025
1026 codename_to_api_level_map is needed to translate the codename which may be
1027 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -07001028 """
Tao Bao76def242017-11-21 09:25:31 -08001029 if codename_to_api_level_map is None:
1030 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -07001031
Alex Klyubin9667b182015-12-10 13:38:50 -08001032 java_library_path = os.path.join(
1033 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1034
Tao Baoe95540e2016-11-08 12:08:53 -08001035 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1036 ["-Djava.library.path=" + java_library_path,
1037 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
1038 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001039 if whole_file:
1040 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001041
1042 min_sdk_version = min_api_level
1043 if min_sdk_version is None:
1044 if not whole_file:
1045 min_sdk_version = GetMinSdkVersionInt(
1046 input_name, codename_to_api_level_map)
1047 if min_sdk_version is not None:
1048 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1049
T.R. Fullhart37e10522013-03-18 10:31:26 -07001050 cmd.extend([key + OPTIONS.public_key_suffix,
1051 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001052 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001053
Tao Bao73dd4f42018-10-04 16:25:33 -07001054 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001055 if password is not None:
1056 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001057 stdoutdata, _ = proc.communicate(password)
1058 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001059 raise ExternalError(
1060 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001061 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001062
Doug Zongkereef39442009-04-02 12:14:19 -07001063
Doug Zongker37974732010-09-16 17:44:38 -07001064def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001065 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001066
Tao Bao9dd909e2017-11-14 11:27:32 -08001067 For non-AVB images, raise exception if the data is too big. Print a warning
1068 if the data is nearing the maximum size.
1069
1070 For AVB images, the actual image size should be identical to the limit.
1071
1072 Args:
1073 data: A string that contains all the data for the partition.
1074 target: The partition name. The ".img" suffix is optional.
1075 info_dict: The dict to be looked up for relevant info.
1076 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001077 if target.endswith(".img"):
1078 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001079 mount_point = "/" + target
1080
Ying Wangf8824af2014-06-03 14:07:27 -07001081 fs_type = None
1082 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001083 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001084 if mount_point == "/userdata":
1085 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001086 p = info_dict["fstab"][mount_point]
1087 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001088 device = p.device
1089 if "/" in device:
1090 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001091 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001092 if not fs_type or not limit:
1093 return
Doug Zongkereef39442009-04-02 12:14:19 -07001094
Andrew Boie0f9aec82012-02-14 09:32:52 -08001095 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001096 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1097 # path.
1098 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1099 if size != limit:
1100 raise ExternalError(
1101 "Mismatching image size for %s: expected %d actual %d" % (
1102 target, limit, size))
1103 else:
1104 pct = float(size) * 100.0 / limit
1105 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1106 if pct >= 99.0:
1107 raise ExternalError(msg)
1108 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001109 logger.warning("\n WARNING: %s\n", msg)
1110 else:
1111 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001112
1113
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001114def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001115 """Parses the APK certs info from a given target-files zip.
1116
1117 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1118 tuple with the following elements: (1) a dictionary that maps packages to
1119 certs (based on the "certificate" and "private_key" attributes in the file;
1120 (2) a string representing the extension of compressed APKs in the target files
1121 (e.g ".gz", ".bro").
1122
1123 Args:
1124 tf_zip: The input target_files ZipFile (already open).
1125
1126 Returns:
1127 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1128 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1129 no compressed APKs.
1130 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001131 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001132 compressed_extension = None
1133
Tao Bao0f990332017-09-08 19:02:54 -07001134 # META/apkcerts.txt contains the info for _all_ the packages known at build
1135 # time. Filter out the ones that are not installed.
1136 installed_files = set()
1137 for name in tf_zip.namelist():
1138 basename = os.path.basename(name)
1139 if basename:
1140 installed_files.add(basename)
1141
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001142 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1143 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001144 if not line:
1145 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001146 m = re.match(
1147 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1148 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1149 line)
1150 if not m:
1151 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001152
Tao Bao818ddf52018-01-05 11:17:34 -08001153 matches = m.groupdict()
1154 cert = matches["CERT"]
1155 privkey = matches["PRIVKEY"]
1156 name = matches["NAME"]
1157 this_compressed_extension = matches["COMPRESSED"]
1158
1159 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1160 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1161 if cert in SPECIAL_CERT_STRINGS and not privkey:
1162 certmap[name] = cert
1163 elif (cert.endswith(OPTIONS.public_key_suffix) and
1164 privkey.endswith(OPTIONS.private_key_suffix) and
1165 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1166 certmap[name] = cert[:-public_key_suffix_len]
1167 else:
1168 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1169
1170 if not this_compressed_extension:
1171 continue
1172
1173 # Only count the installed files.
1174 filename = name + '.' + this_compressed_extension
1175 if filename not in installed_files:
1176 continue
1177
1178 # Make sure that all the values in the compression map have the same
1179 # extension. We don't support multiple compression methods in the same
1180 # system image.
1181 if compressed_extension:
1182 if this_compressed_extension != compressed_extension:
1183 raise ValueError(
1184 "Multiple compressed extensions: {} vs {}".format(
1185 compressed_extension, this_compressed_extension))
1186 else:
1187 compressed_extension = this_compressed_extension
1188
1189 return (certmap,
1190 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001191
1192
Doug Zongkereef39442009-04-02 12:14:19 -07001193COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001194Global options
1195
1196 -p (--path) <dir>
1197 Prepend <dir>/bin to the list of places to search for binaries run by this
1198 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001199
Doug Zongker05d3dea2009-06-22 11:32:31 -07001200 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001201 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001202
Tao Bao30df8b42018-04-23 15:32:53 -07001203 -x (--extra) <key=value>
1204 Add a key/value pair to the 'extras' dict, which device-specific extension
1205 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001206
Doug Zongkereef39442009-04-02 12:14:19 -07001207 -v (--verbose)
1208 Show command lines being executed.
1209
1210 -h (--help)
1211 Display this usage message and exit.
1212"""
1213
1214def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001215 print(docstring.rstrip("\n"))
1216 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001217
1218
1219def ParseOptions(argv,
1220 docstring,
1221 extra_opts="", extra_long_opts=(),
1222 extra_option_handler=None):
1223 """Parse the options in argv and return any arguments that aren't
1224 flags. docstring is the calling module's docstring, to be displayed
1225 for errors and -h. extra_opts and extra_long_opts are for flags
1226 defined by the caller, which are processed by passing them to
1227 extra_option_handler."""
1228
1229 try:
1230 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001231 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001232 ["help", "verbose", "path=", "signapk_path=",
1233 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001234 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001235 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1236 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001237 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001238 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001239 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001240 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001241 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001242 sys.exit(2)
1243
Doug Zongkereef39442009-04-02 12:14:19 -07001244 for o, a in opts:
1245 if o in ("-h", "--help"):
1246 Usage(docstring)
1247 sys.exit()
1248 elif o in ("-v", "--verbose"):
1249 OPTIONS.verbose = True
1250 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001251 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001252 elif o in ("--signapk_path",):
1253 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001254 elif o in ("--signapk_shared_library_path",):
1255 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001256 elif o in ("--extra_signapk_args",):
1257 OPTIONS.extra_signapk_args = shlex.split(a)
1258 elif o in ("--java_path",):
1259 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001260 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001261 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001262 elif o in ("--public_key_suffix",):
1263 OPTIONS.public_key_suffix = a
1264 elif o in ("--private_key_suffix",):
1265 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001266 elif o in ("--boot_signer_path",):
1267 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001268 elif o in ("--boot_signer_args",):
1269 OPTIONS.boot_signer_args = shlex.split(a)
1270 elif o in ("--verity_signer_path",):
1271 OPTIONS.verity_signer_path = a
1272 elif o in ("--verity_signer_args",):
1273 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001274 elif o in ("-s", "--device_specific"):
1275 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001276 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001277 key, value = a.split("=", 1)
1278 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001279 else:
1280 if extra_option_handler is None or not extra_option_handler(o, a):
1281 assert False, "unknown option \"%s\"" % (o,)
1282
Doug Zongker85448772014-09-09 14:59:20 -07001283 if OPTIONS.search_path:
1284 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1285 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001286
1287 return args
1288
1289
Tao Bao4c851b12016-09-19 13:54:38 -07001290def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001291 """Make a temp file and add it to the list of things to be deleted
1292 when Cleanup() is called. Return the filename."""
1293 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1294 os.close(fd)
1295 OPTIONS.tempfiles.append(fn)
1296 return fn
1297
1298
Tao Bao1c830bf2017-12-25 10:43:47 -08001299def MakeTempDir(prefix='tmp', suffix=''):
1300 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1301
1302 Returns:
1303 The absolute pathname of the new directory.
1304 """
1305 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1306 OPTIONS.tempfiles.append(dir_name)
1307 return dir_name
1308
1309
Doug Zongkereef39442009-04-02 12:14:19 -07001310def Cleanup():
1311 for i in OPTIONS.tempfiles:
1312 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001313 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001314 else:
1315 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001316 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001317
1318
1319class PasswordManager(object):
1320 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001321 self.editor = os.getenv("EDITOR")
1322 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001323
1324 def GetPasswords(self, items):
1325 """Get passwords corresponding to each string in 'items',
1326 returning a dict. (The dict may have keys in addition to the
1327 values in 'items'.)
1328
1329 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1330 user edit that file to add more needed passwords. If no editor is
1331 available, or $ANDROID_PW_FILE isn't define, prompts the user
1332 interactively in the ordinary way.
1333 """
1334
1335 current = self.ReadFile()
1336
1337 first = True
1338 while True:
1339 missing = []
1340 for i in items:
1341 if i not in current or not current[i]:
1342 missing.append(i)
1343 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001344 if not missing:
1345 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001346
1347 for i in missing:
1348 current[i] = ""
1349
1350 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001351 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001352 answer = raw_input("try to edit again? [y]> ").strip()
1353 if answer and answer[0] not in 'yY':
1354 raise RuntimeError("key passwords unavailable")
1355 first = False
1356
1357 current = self.UpdateAndReadFile(current)
1358
Dan Albert8b72aef2015-03-23 19:13:21 -07001359 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001360 """Prompt the user to enter a value (password) for each key in
1361 'current' whose value is fales. Returns a new dict with all the
1362 values.
1363 """
1364 result = {}
1365 for k, v in sorted(current.iteritems()):
1366 if v:
1367 result[k] = v
1368 else:
1369 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001370 result[k] = getpass.getpass(
1371 "Enter password for %s key> " % k).strip()
1372 if result[k]:
1373 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001374 return result
1375
1376 def UpdateAndReadFile(self, current):
1377 if not self.editor or not self.pwfile:
1378 return self.PromptResult(current)
1379
1380 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001381 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001382 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1383 f.write("# (Additional spaces are harmless.)\n\n")
1384
1385 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001386 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1387 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001388 f.write("[[[ %s ]]] %s\n" % (v, k))
1389 if not v and first_line is None:
1390 # position cursor on first line with no password.
1391 first_line = i + 4
1392 f.close()
1393
Tao Bao986ee862018-10-04 15:46:16 -07001394 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001395
1396 return self.ReadFile()
1397
1398 def ReadFile(self):
1399 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001400 if self.pwfile is None:
1401 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001402 try:
1403 f = open(self.pwfile, "r")
1404 for line in f:
1405 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001406 if not line or line[0] == '#':
1407 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001408 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1409 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001410 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001411 else:
1412 result[m.group(2)] = m.group(1)
1413 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001414 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001415 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001416 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001417 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001418
1419
Dan Albert8e0178d2015-01-27 15:53:15 -08001420def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1421 compress_type=None):
1422 import datetime
1423
1424 # http://b/18015246
1425 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1426 # for files larger than 2GiB. We can work around this by adjusting their
1427 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1428 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1429 # it isn't clear to me exactly what circumstances cause this).
1430 # `zipfile.write()` must be used directly to work around this.
1431 #
1432 # This mess can be avoided if we port to python3.
1433 saved_zip64_limit = zipfile.ZIP64_LIMIT
1434 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1435
1436 if compress_type is None:
1437 compress_type = zip_file.compression
1438 if arcname is None:
1439 arcname = filename
1440
1441 saved_stat = os.stat(filename)
1442
1443 try:
1444 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1445 # file to be zipped and reset it when we're done.
1446 os.chmod(filename, perms)
1447
1448 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001449 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1450 # intentional. zip stores datetimes in local time without a time zone
1451 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1452 # in the zip archive.
1453 local_epoch = datetime.datetime.fromtimestamp(0)
1454 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001455 os.utime(filename, (timestamp, timestamp))
1456
1457 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1458 finally:
1459 os.chmod(filename, saved_stat.st_mode)
1460 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1461 zipfile.ZIP64_LIMIT = saved_zip64_limit
1462
1463
Tao Bao58c1b962015-05-20 09:32:18 -07001464def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001465 compress_type=None):
1466 """Wrap zipfile.writestr() function to work around the zip64 limit.
1467
1468 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1469 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1470 when calling crc32(bytes).
1471
1472 But it still works fine to write a shorter string into a large zip file.
1473 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1474 when we know the string won't be too long.
1475 """
1476
1477 saved_zip64_limit = zipfile.ZIP64_LIMIT
1478 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1479
1480 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1481 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001482 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001483 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001484 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001485 else:
Tao Baof3282b42015-04-01 11:21:55 -07001486 zinfo = zinfo_or_arcname
1487
1488 # If compress_type is given, it overrides the value in zinfo.
1489 if compress_type is not None:
1490 zinfo.compress_type = compress_type
1491
Tao Bao58c1b962015-05-20 09:32:18 -07001492 # If perms is given, it has a priority.
1493 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001494 # If perms doesn't set the file type, mark it as a regular file.
1495 if perms & 0o770000 == 0:
1496 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001497 zinfo.external_attr = perms << 16
1498
Tao Baof3282b42015-04-01 11:21:55 -07001499 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001500 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1501
Dan Albert8b72aef2015-03-23 19:13:21 -07001502 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001503 zipfile.ZIP64_LIMIT = saved_zip64_limit
1504
1505
Tao Bao89d7ab22017-12-14 17:05:33 -08001506def ZipDelete(zip_filename, entries):
1507 """Deletes entries from a ZIP file.
1508
1509 Since deleting entries from a ZIP file is not supported, it shells out to
1510 'zip -d'.
1511
1512 Args:
1513 zip_filename: The name of the ZIP file.
1514 entries: The name of the entry, or the list of names to be deleted.
1515
1516 Raises:
1517 AssertionError: In case of non-zero return from 'zip'.
1518 """
1519 if isinstance(entries, basestring):
1520 entries = [entries]
1521 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001522 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001523
1524
Tao Baof3282b42015-04-01 11:21:55 -07001525def ZipClose(zip_file):
1526 # http://b/18015246
1527 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1528 # central directory.
1529 saved_zip64_limit = zipfile.ZIP64_LIMIT
1530 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1531
1532 zip_file.close()
1533
1534 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001535
1536
1537class DeviceSpecificParams(object):
1538 module = None
1539 def __init__(self, **kwargs):
1540 """Keyword arguments to the constructor become attributes of this
1541 object, which is passed to all functions in the device-specific
1542 module."""
1543 for k, v in kwargs.iteritems():
1544 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001545 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001546
1547 if self.module is None:
1548 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001549 if not path:
1550 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001551 try:
1552 if os.path.isdir(path):
1553 info = imp.find_module("releasetools", [path])
1554 else:
1555 d, f = os.path.split(path)
1556 b, x = os.path.splitext(f)
1557 if x == ".py":
1558 f = b
1559 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001560 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001561 self.module = imp.load_module("device_specific", *info)
1562 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001563 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001564
1565 def _DoCall(self, function_name, *args, **kwargs):
1566 """Call the named function in the device-specific module, passing
1567 the given args and kwargs. The first argument to the call will be
1568 the DeviceSpecific object itself. If there is no module, or the
1569 module does not define the function, return the value of the
1570 'default' kwarg (which itself defaults to None)."""
1571 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001572 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001573 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1574
1575 def FullOTA_Assertions(self):
1576 """Called after emitting the block of assertions at the top of a
1577 full OTA package. Implementations can add whatever additional
1578 assertions they like."""
1579 return self._DoCall("FullOTA_Assertions")
1580
Doug Zongkere5ff5902012-01-17 10:55:37 -08001581 def FullOTA_InstallBegin(self):
1582 """Called at the start of full OTA installation."""
1583 return self._DoCall("FullOTA_InstallBegin")
1584
Yifan Hong10c530d2018-12-27 17:34:18 -08001585 def FullOTA_GetBlockDifferences(self):
1586 """Called during full OTA installation and verification.
1587 Implementation should return a list of BlockDifference objects describing
1588 the update on each additional partitions.
1589 """
1590 return self._DoCall("FullOTA_GetBlockDifferences")
1591
Doug Zongker05d3dea2009-06-22 11:32:31 -07001592 def FullOTA_InstallEnd(self):
1593 """Called at the end of full OTA installation; typically this is
1594 used to install the image for the device's baseband processor."""
1595 return self._DoCall("FullOTA_InstallEnd")
1596
1597 def IncrementalOTA_Assertions(self):
1598 """Called after emitting the block of assertions at the top of an
1599 incremental OTA package. Implementations can add whatever
1600 additional assertions they like."""
1601 return self._DoCall("IncrementalOTA_Assertions")
1602
Doug Zongkere5ff5902012-01-17 10:55:37 -08001603 def IncrementalOTA_VerifyBegin(self):
1604 """Called at the start of the verification phase of incremental
1605 OTA installation; additional checks can be placed here to abort
1606 the script before any changes are made."""
1607 return self._DoCall("IncrementalOTA_VerifyBegin")
1608
Doug Zongker05d3dea2009-06-22 11:32:31 -07001609 def IncrementalOTA_VerifyEnd(self):
1610 """Called at the end of the verification phase of incremental OTA
1611 installation; additional checks can be placed here to abort the
1612 script before any changes are made."""
1613 return self._DoCall("IncrementalOTA_VerifyEnd")
1614
Doug Zongkere5ff5902012-01-17 10:55:37 -08001615 def IncrementalOTA_InstallBegin(self):
1616 """Called at the start of incremental OTA installation (after
1617 verification is complete)."""
1618 return self._DoCall("IncrementalOTA_InstallBegin")
1619
Yifan Hong10c530d2018-12-27 17:34:18 -08001620 def IncrementalOTA_GetBlockDifferences(self):
1621 """Called during incremental OTA installation and verification.
1622 Implementation should return a list of BlockDifference objects describing
1623 the update on each additional partitions.
1624 """
1625 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1626
Doug Zongker05d3dea2009-06-22 11:32:31 -07001627 def IncrementalOTA_InstallEnd(self):
1628 """Called at the end of incremental OTA installation; typically
1629 this is used to install the image for the device's baseband
1630 processor."""
1631 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001632
Tao Bao9bc6bb22015-11-09 16:58:28 -08001633 def VerifyOTA_Assertions(self):
1634 return self._DoCall("VerifyOTA_Assertions")
1635
Tao Bao76def242017-11-21 09:25:31 -08001636
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001637class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001638 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001639 self.name = name
1640 self.data = data
1641 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001642 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001643 self.sha1 = sha1(data).hexdigest()
1644
1645 @classmethod
1646 def FromLocalFile(cls, name, diskname):
1647 f = open(diskname, "rb")
1648 data = f.read()
1649 f.close()
1650 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001651
1652 def WriteToTemp(self):
1653 t = tempfile.NamedTemporaryFile()
1654 t.write(self.data)
1655 t.flush()
1656 return t
1657
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001658 def WriteToDir(self, d):
1659 with open(os.path.join(d, self.name), "wb") as fp:
1660 fp.write(self.data)
1661
Geremy Condra36bd3652014-02-06 19:45:10 -08001662 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001663 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001664
Tao Bao76def242017-11-21 09:25:31 -08001665
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001666DIFF_PROGRAM_BY_EXT = {
1667 ".gz" : "imgdiff",
1668 ".zip" : ["imgdiff", "-z"],
1669 ".jar" : ["imgdiff", "-z"],
1670 ".apk" : ["imgdiff", "-z"],
1671 ".img" : "imgdiff",
1672 }
1673
Tao Bao76def242017-11-21 09:25:31 -08001674
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001675class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001676 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001677 self.tf = tf
1678 self.sf = sf
1679 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001680 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001681
1682 def ComputePatch(self):
1683 """Compute the patch (as a string of data) needed to turn sf into
1684 tf. Returns the same tuple as GetPatch()."""
1685
1686 tf = self.tf
1687 sf = self.sf
1688
Doug Zongker24cd2802012-08-14 16:36:15 -07001689 if self.diff_program:
1690 diff_program = self.diff_program
1691 else:
1692 ext = os.path.splitext(tf.name)[1]
1693 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001694
1695 ttemp = tf.WriteToTemp()
1696 stemp = sf.WriteToTemp()
1697
1698 ext = os.path.splitext(tf.name)[1]
1699
1700 try:
1701 ptemp = tempfile.NamedTemporaryFile()
1702 if isinstance(diff_program, list):
1703 cmd = copy.copy(diff_program)
1704 else:
1705 cmd = [diff_program]
1706 cmd.append(stemp.name)
1707 cmd.append(ttemp.name)
1708 cmd.append(ptemp.name)
1709 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001710 err = []
1711 def run():
1712 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001713 if e:
1714 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001715 th = threading.Thread(target=run)
1716 th.start()
1717 th.join(timeout=300) # 5 mins
1718 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001719 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001720 p.terminate()
1721 th.join(5)
1722 if th.is_alive():
1723 p.kill()
1724 th.join()
1725
Tianjie Xua2a9f992018-01-05 15:15:54 -08001726 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001727 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001728 self.patch = None
1729 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001730 diff = ptemp.read()
1731 finally:
1732 ptemp.close()
1733 stemp.close()
1734 ttemp.close()
1735
1736 self.patch = diff
1737 return self.tf, self.sf, self.patch
1738
1739
1740 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001741 """Returns a tuple of (target_file, source_file, patch_data).
1742
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001743 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001744 computing the patch failed.
1745 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001746 return self.tf, self.sf, self.patch
1747
1748
1749def ComputeDifferences(diffs):
1750 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001751 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001752
1753 # Do the largest files first, to try and reduce the long-pole effect.
1754 by_size = [(i.tf.size, i) for i in diffs]
1755 by_size.sort(reverse=True)
1756 by_size = [i[1] for i in by_size]
1757
1758 lock = threading.Lock()
1759 diff_iter = iter(by_size) # accessed under lock
1760
1761 def worker():
1762 try:
1763 lock.acquire()
1764 for d in diff_iter:
1765 lock.release()
1766 start = time.time()
1767 d.ComputePatch()
1768 dur = time.time() - start
1769 lock.acquire()
1770
1771 tf, sf, patch = d.GetPatch()
1772 if sf.name == tf.name:
1773 name = tf.name
1774 else:
1775 name = "%s (%s)" % (tf.name, sf.name)
1776 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001777 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001778 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001779 logger.info(
1780 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1781 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001782 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001783 except Exception:
1784 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001785 raise
1786
1787 # start worker threads; wait for them all to finish.
1788 threads = [threading.Thread(target=worker)
1789 for i in range(OPTIONS.worker_threads)]
1790 for th in threads:
1791 th.start()
1792 while threads:
1793 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001794
1795
Dan Albert8b72aef2015-03-23 19:13:21 -07001796class BlockDifference(object):
1797 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001798 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001799 self.tgt = tgt
1800 self.src = src
1801 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001802 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001803 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001804
Tao Baodd2a5892015-03-12 12:32:37 -07001805 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001806 version = max(
1807 int(i) for i in
1808 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001809 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001810 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001811
1812 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001813 version=self.version,
1814 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001815 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001816 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001817 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001818 self.touched_src_ranges = b.touched_src_ranges
1819 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001820
Yifan Hong10c530d2018-12-27 17:34:18 -08001821 # On devices with dynamic partitions, for new partitions,
1822 # src is None but OPTIONS.source_info_dict is not.
1823 if OPTIONS.source_info_dict is None:
1824 is_dynamic_build = OPTIONS.info_dict.get(
1825 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001826 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001827 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001828 is_dynamic_build = OPTIONS.source_info_dict.get(
1829 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001830 is_dynamic_source = partition in shlex.split(
1831 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001832
Yifan Hongbb2658d2019-01-25 12:30:58 -08001833 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001834 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1835
Yifan Hongbb2658d2019-01-25 12:30:58 -08001836 # For dynamic partitions builds, check partition list in both source
1837 # and target build because new partitions may be added, and existing
1838 # partitions may be removed.
1839 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1840
Yifan Hong10c530d2018-12-27 17:34:18 -08001841 if is_dynamic:
1842 self.device = 'map_partition("%s")' % partition
1843 else:
1844 if OPTIONS.source_info_dict is None:
1845 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1846 else:
1847 _, device_path = GetTypeAndDevice("/" + partition,
1848 OPTIONS.source_info_dict)
1849 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001850
Tao Baod8d14be2016-02-04 14:26:02 -08001851 @property
1852 def required_cache(self):
1853 return self._required_cache
1854
Tao Bao76def242017-11-21 09:25:31 -08001855 def WriteScript(self, script, output_zip, progress=None,
1856 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001857 if not self.src:
1858 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001859 script.Print("Patching %s image unconditionally..." % (self.partition,))
1860 else:
1861 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001862
Dan Albert8b72aef2015-03-23 19:13:21 -07001863 if progress:
1864 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001865 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001866
1867 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001868 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001869
Tao Bao9bc6bb22015-11-09 16:58:28 -08001870 def WriteStrictVerifyScript(self, script):
1871 """Verify all the blocks in the care_map, including clobbered blocks.
1872
1873 This differs from the WriteVerifyScript() function: a) it prints different
1874 error messages; b) it doesn't allow half-way updated images to pass the
1875 verification."""
1876
1877 partition = self.partition
1878 script.Print("Verifying %s..." % (partition,))
1879 ranges = self.tgt.care_map
1880 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001881 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001882 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1883 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001884 self.device, ranges_str,
1885 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001886 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001887 script.AppendExtra("")
1888
Tao Baod522bdc2016-04-12 15:53:16 -07001889 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001890 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001891
1892 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001893 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001894 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001895
1896 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001897 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001898 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001899 ranges = self.touched_src_ranges
1900 expected_sha1 = self.touched_src_sha1
1901 else:
1902 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1903 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001904
1905 # No blocks to be checked, skipping.
1906 if not ranges:
1907 return
1908
Tao Bao5ece99d2015-05-12 11:42:31 -07001909 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001910 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001911 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001912 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1913 '"%s.patch.dat")) then' % (
1914 self.device, ranges_str, expected_sha1,
1915 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001916 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001917 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001918
Tianjie Xufc3422a2015-12-15 11:53:59 -08001919 if self.version >= 4:
1920
1921 # Bug: 21124327
1922 # When generating incrementals for the system and vendor partitions in
1923 # version 4 or newer, explicitly check the first block (which contains
1924 # the superblock) of the partition to see if it's what we expect. If
1925 # this check fails, give an explicit log message about the partition
1926 # having been remounted R/W (the most likely explanation).
1927 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08001928 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08001929
1930 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001931 if partition == "system":
1932 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1933 else:
1934 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001935 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08001936 'ifelse (block_image_recover({device}, "{ranges}") && '
1937 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001938 'package_extract_file("{partition}.transfer.list"), '
1939 '"{partition}.new.dat", "{partition}.patch.dat"), '
1940 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001941 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001942 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001943 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001944
Tao Baodd2a5892015-03-12 12:32:37 -07001945 # Abort the OTA update. Note that the incremental OTA cannot be applied
1946 # even if it may match the checksum of the target partition.
1947 # a) If version < 3, operations like move and erase will make changes
1948 # unconditionally and damage the partition.
1949 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001950 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001951 if partition == "system":
1952 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1953 else:
1954 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1955 script.AppendExtra((
1956 'abort("E%d: %s partition has unexpected contents");\n'
1957 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001958
Yifan Hong10c530d2018-12-27 17:34:18 -08001959 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07001960 partition = self.partition
1961 script.Print('Verifying the updated %s image...' % (partition,))
1962 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1963 ranges = self.tgt.care_map
1964 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001965 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001966 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001967 self.device, ranges_str,
1968 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001969
1970 # Bug: 20881595
1971 # Verify that extended blocks are really zeroed out.
1972 if self.tgt.extended:
1973 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001974 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001975 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001976 self.device, ranges_str,
1977 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001978 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001979 if partition == "system":
1980 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1981 else:
1982 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001983 script.AppendExtra(
1984 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001985 ' abort("E%d: %s partition has unexpected non-zero contents after '
1986 'OTA update");\n'
1987 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001988 else:
1989 script.Print('Verified the updated %s image.' % (partition,))
1990
Tianjie Xu209db462016-05-24 17:34:52 -07001991 if partition == "system":
1992 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1993 else:
1994 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1995
Tao Bao5fcaaef2015-06-01 13:40:49 -07001996 script.AppendExtra(
1997 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001998 ' abort("E%d: %s partition has unexpected contents after OTA '
1999 'update");\n'
2000 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002001
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002002 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002003 ZipWrite(output_zip,
2004 '{}.transfer.list'.format(self.path),
2005 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002006
Tao Bao76def242017-11-21 09:25:31 -08002007 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2008 # its size. Quailty 9 almost triples the compression time but doesn't
2009 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002010 # zip | brotli(quality 6) | brotli(quality 9)
2011 # compressed_size: 942M | 869M (~8% reduced) | 854M
2012 # compression_time: 75s | 265s | 719s
2013 # decompression_time: 15s | 25s | 25s
2014
2015 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002016 brotli_cmd = ['brotli', '--quality=6',
2017 '--output={}.new.dat.br'.format(self.path),
2018 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002019 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002020 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002021
2022 new_data_name = '{}.new.dat.br'.format(self.partition)
2023 ZipWrite(output_zip,
2024 '{}.new.dat.br'.format(self.path),
2025 new_data_name,
2026 compress_type=zipfile.ZIP_STORED)
2027 else:
2028 new_data_name = '{}.new.dat'.format(self.partition)
2029 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2030
Dan Albert8e0178d2015-01-27 15:53:15 -08002031 ZipWrite(output_zip,
2032 '{}.patch.dat'.format(self.path),
2033 '{}.patch.dat'.format(self.partition),
2034 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002035
Tianjie Xu209db462016-05-24 17:34:52 -07002036 if self.partition == "system":
2037 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2038 else:
2039 code = ErrorCode.VENDOR_UPDATE_FAILURE
2040
Yifan Hong10c530d2018-12-27 17:34:18 -08002041 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002042 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002043 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002044 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002045 device=self.device, partition=self.partition,
2046 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002047 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002048
Dan Albert8b72aef2015-03-23 19:13:21 -07002049 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002050 data = source.ReadRangeSet(ranges)
2051 ctx = sha1()
2052
2053 for p in data:
2054 ctx.update(p)
2055
2056 return ctx.hexdigest()
2057
Tao Baoe9b61912015-07-09 17:37:49 -07002058 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2059 """Return the hash value for all zero blocks."""
2060 zero_block = '\x00' * 4096
2061 ctx = sha1()
2062 for _ in range(num_blocks):
2063 ctx.update(zero_block)
2064
2065 return ctx.hexdigest()
2066
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002067
2068DataImage = blockimgdiff.DataImage
2069
Tao Bao76def242017-11-21 09:25:31 -08002070
Doug Zongker96a57e72010-09-26 14:57:41 -07002071# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002072PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002073 "ext4": "EMMC",
2074 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002075 "f2fs": "EMMC",
2076 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002077}
Doug Zongker96a57e72010-09-26 14:57:41 -07002078
Tao Bao76def242017-11-21 09:25:31 -08002079
Doug Zongker96a57e72010-09-26 14:57:41 -07002080def GetTypeAndDevice(mount_point, info):
2081 fstab = info["fstab"]
2082 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002083 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2084 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002085 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002086 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002087
2088
2089def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002090 """Parses and converts a PEM-encoded certificate into DER-encoded.
2091
2092 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2093
2094 Returns:
2095 The decoded certificate string.
2096 """
2097 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002098 save = False
2099 for line in data.split("\n"):
2100 if "--END CERTIFICATE--" in line:
2101 break
2102 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002103 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002104 if "--BEGIN CERTIFICATE--" in line:
2105 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002106 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002107 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002108
Tao Bao04e1f012018-02-04 12:13:35 -08002109
2110def ExtractPublicKey(cert):
2111 """Extracts the public key (PEM-encoded) from the given certificate file.
2112
2113 Args:
2114 cert: The certificate filename.
2115
2116 Returns:
2117 The public key string.
2118
2119 Raises:
2120 AssertionError: On non-zero return from 'openssl'.
2121 """
2122 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2123 # While openssl 1.1 writes the key into the given filename followed by '-out',
2124 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2125 # stdout instead.
2126 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2127 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2128 pubkey, stderrdata = proc.communicate()
2129 assert proc.returncode == 0, \
2130 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2131 return pubkey
2132
2133
Tao Bao2cc0ca12019-03-15 10:44:43 -07002134def ExtractAvbPublicKey(key):
2135 """Extracts the AVB public key from the given public or private key.
2136
2137 Args:
2138 key: The input key file, which should be PEM-encoded public or private key.
2139
2140 Returns:
2141 The path to the extracted AVB public key file.
2142 """
2143 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2144 RunAndCheckOutput(
2145 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2146 return output
2147
2148
Doug Zongker412c02f2014-02-13 10:58:24 -08002149def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2150 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002151 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002152
Tao Bao6d5d6232018-03-09 17:04:42 -08002153 Most of the space in the boot and recovery images is just the kernel, which is
2154 identical for the two, so the resulting patch should be efficient. Add it to
2155 the output zip, along with a shell script that is run from init.rc on first
2156 boot to actually do the patching and install the new recovery image.
2157
2158 Args:
2159 input_dir: The top-level input directory of the target-files.zip.
2160 output_sink: The callback function that writes the result.
2161 recovery_img: File object for the recovery image.
2162 boot_img: File objects for the boot image.
2163 info_dict: A dict returned by common.LoadInfoDict() on the input
2164 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002165 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002166 if info_dict is None:
2167 info_dict = OPTIONS.info_dict
2168
Tao Bao6d5d6232018-03-09 17:04:42 -08002169 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002170
Tao Baof2cffbd2015-07-22 12:33:18 -07002171 if full_recovery_image:
2172 output_sink("etc/recovery.img", recovery_img.data)
2173
2174 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002175 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002176 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002177 # With system-root-image, boot and recovery images will have mismatching
2178 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2179 # to handle such a case.
2180 if system_root_image:
2181 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002182 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002183 assert not os.path.exists(path)
2184 else:
2185 diff_program = ["imgdiff"]
2186 if os.path.exists(path):
2187 diff_program.append("-b")
2188 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002189 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002190 else:
2191 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002192
2193 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2194 _, _, patch = d.ComputePatch()
2195 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002196
Dan Albertebb19aa2015-03-27 19:11:53 -07002197 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002198 # The following GetTypeAndDevice()s need to use the path in the target
2199 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002200 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2201 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2202 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002203 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002204
Tao Baof2cffbd2015-07-22 12:33:18 -07002205 if full_recovery_image:
2206 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002207if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2208 applypatch \\
2209 --flash /system/etc/recovery.img \\
2210 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2211 log -t recovery "Installing new recovery image: succeeded" || \\
2212 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002213else
2214 log -t recovery "Recovery image already installed"
2215fi
2216""" % {'type': recovery_type,
2217 'device': recovery_device,
2218 'sha1': recovery_img.sha1,
2219 'size': recovery_img.size}
2220 else:
2221 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002222if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2223 applypatch %(bonus_args)s \\
2224 --patch /system/recovery-from-boot.p \\
2225 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2226 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2227 log -t recovery "Installing new recovery image: succeeded" || \\
2228 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002229else
2230 log -t recovery "Recovery image already installed"
2231fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002232""" % {'boot_size': boot_img.size,
2233 'boot_sha1': boot_img.sha1,
2234 'recovery_size': recovery_img.size,
2235 'recovery_sha1': recovery_img.sha1,
2236 'boot_type': boot_type,
2237 'boot_device': boot_device,
2238 'recovery_type': recovery_type,
2239 'recovery_device': recovery_device,
2240 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002241
2242 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002243 # in the L release.
2244 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002245
Tao Bao32fcdab2018-10-12 10:30:39 -07002246 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002247
2248 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002249
2250
2251class DynamicPartitionUpdate(object):
2252 def __init__(self, src_group=None, tgt_group=None, progress=None,
2253 block_difference=None):
2254 self.src_group = src_group
2255 self.tgt_group = tgt_group
2256 self.progress = progress
2257 self.block_difference = block_difference
2258
2259 @property
2260 def src_size(self):
2261 if not self.block_difference:
2262 return 0
2263 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2264
2265 @property
2266 def tgt_size(self):
2267 if not self.block_difference:
2268 return 0
2269 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2270
2271 @staticmethod
2272 def _GetSparseImageSize(img):
2273 if not img:
2274 return 0
2275 return img.blocksize * img.total_blocks
2276
2277
2278class DynamicGroupUpdate(object):
2279 def __init__(self, src_size=None, tgt_size=None):
2280 # None: group does not exist. 0: no size limits.
2281 self.src_size = src_size
2282 self.tgt_size = tgt_size
2283
2284
2285class DynamicPartitionsDifference(object):
2286 def __init__(self, info_dict, block_diffs, progress_dict=None,
2287 source_info_dict=None):
2288 if progress_dict is None:
2289 progress_dict = dict()
2290
2291 self._remove_all_before_apply = False
2292 if source_info_dict is None:
2293 self._remove_all_before_apply = True
2294 source_info_dict = dict()
2295
2296 block_diff_dict = {e.partition:e for e in block_diffs}
2297 assert len(block_diff_dict) == len(block_diffs), \
2298 "Duplicated BlockDifference object for {}".format(
2299 [partition for partition, count in
2300 collections.Counter(e.partition for e in block_diffs).items()
2301 if count > 1])
2302
Yifan Hong79997e52019-01-23 16:56:19 -08002303 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002304
2305 for p, block_diff in block_diff_dict.items():
2306 self._partition_updates[p] = DynamicPartitionUpdate()
2307 self._partition_updates[p].block_difference = block_diff
2308
2309 for p, progress in progress_dict.items():
2310 if p in self._partition_updates:
2311 self._partition_updates[p].progress = progress
2312
2313 tgt_groups = shlex.split(info_dict.get(
2314 "super_partition_groups", "").strip())
2315 src_groups = shlex.split(source_info_dict.get(
2316 "super_partition_groups", "").strip())
2317
2318 for g in tgt_groups:
2319 for p in shlex.split(info_dict.get(
2320 "super_%s_partition_list" % g, "").strip()):
2321 assert p in self._partition_updates, \
2322 "{} is in target super_{}_partition_list but no BlockDifference " \
2323 "object is provided.".format(p, g)
2324 self._partition_updates[p].tgt_group = g
2325
2326 for g in src_groups:
2327 for p in shlex.split(source_info_dict.get(
2328 "super_%s_partition_list" % g, "").strip()):
2329 assert p in self._partition_updates, \
2330 "{} is in source super_{}_partition_list but no BlockDifference " \
2331 "object is provided.".format(p, g)
2332 self._partition_updates[p].src_group = g
2333
Yifan Hong45433e42019-01-18 13:55:25 -08002334 target_dynamic_partitions = set(shlex.split(info_dict.get(
2335 "dynamic_partition_list", "").strip()))
2336 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2337 if u.tgt_size)
2338 assert block_diffs_with_target == target_dynamic_partitions, \
2339 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2340 list(target_dynamic_partitions), list(block_diffs_with_target))
2341
2342 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2343 "dynamic_partition_list", "").strip()))
2344 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2345 if u.src_size)
2346 assert block_diffs_with_source == source_dynamic_partitions, \
2347 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2348 list(source_dynamic_partitions), list(block_diffs_with_source))
2349
Yifan Hong10c530d2018-12-27 17:34:18 -08002350 if self._partition_updates:
2351 logger.info("Updating dynamic partitions %s",
2352 self._partition_updates.keys())
2353
Yifan Hong79997e52019-01-23 16:56:19 -08002354 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002355
2356 for g in tgt_groups:
2357 self._group_updates[g] = DynamicGroupUpdate()
2358 self._group_updates[g].tgt_size = int(info_dict.get(
2359 "super_%s_group_size" % g, "0").strip())
2360
2361 for g in src_groups:
2362 if g not in self._group_updates:
2363 self._group_updates[g] = DynamicGroupUpdate()
2364 self._group_updates[g].src_size = int(source_info_dict.get(
2365 "super_%s_group_size" % g, "0").strip())
2366
2367 self._Compute()
2368
2369 def WriteScript(self, script, output_zip, write_verify_script=False):
2370 script.Comment('--- Start patching dynamic partitions ---')
2371 for p, u in self._partition_updates.items():
2372 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2373 script.Comment('Patch partition %s' % p)
2374 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2375 write_verify_script=False)
2376
2377 op_list_path = MakeTempFile()
2378 with open(op_list_path, 'w') as f:
2379 for line in self._op_list:
2380 f.write('{}\n'.format(line))
2381
2382 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2383
2384 script.Comment('Update dynamic partition metadata')
2385 script.AppendExtra('assert(update_dynamic_partitions('
2386 'package_extract_file("dynamic_partitions_op_list")));')
2387
2388 if write_verify_script:
2389 for p, u in self._partition_updates.items():
2390 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2391 u.block_difference.WritePostInstallVerifyScript(script)
2392 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2393
2394 for p, u in self._partition_updates.items():
2395 if u.tgt_size and u.src_size <= u.tgt_size:
2396 script.Comment('Patch partition %s' % p)
2397 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2398 write_verify_script=write_verify_script)
2399 if write_verify_script:
2400 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2401
2402 script.Comment('--- End patching dynamic partitions ---')
2403
2404 def _Compute(self):
2405 self._op_list = list()
2406
2407 def append(line):
2408 self._op_list.append(line)
2409
2410 def comment(line):
2411 self._op_list.append("# %s" % line)
2412
2413 if self._remove_all_before_apply:
2414 comment('Remove all existing dynamic partitions and groups before '
2415 'applying full OTA')
2416 append('remove_all_groups')
2417
2418 for p, u in self._partition_updates.items():
2419 if u.src_group and not u.tgt_group:
2420 append('remove %s' % p)
2421
2422 for p, u in self._partition_updates.items():
2423 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2424 comment('Move partition %s from %s to default' % (p, u.src_group))
2425 append('move %s default' % p)
2426
2427 for p, u in self._partition_updates.items():
2428 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2429 comment('Shrink partition %s from %d to %d' %
2430 (p, u.src_size, u.tgt_size))
2431 append('resize %s %s' % (p, u.tgt_size))
2432
2433 for g, u in self._group_updates.items():
2434 if u.src_size is not None and u.tgt_size is None:
2435 append('remove_group %s' % g)
2436 if (u.src_size is not None and u.tgt_size is not None and
2437 u.src_size > u.tgt_size):
2438 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2439 append('resize_group %s %d' % (g, u.tgt_size))
2440
2441 for g, u in self._group_updates.items():
2442 if u.src_size is None and u.tgt_size is not None:
2443 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2444 append('add_group %s %d' % (g, u.tgt_size))
2445 if (u.src_size is not None and u.tgt_size is not None and
2446 u.src_size < u.tgt_size):
2447 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2448 append('resize_group %s %d' % (g, u.tgt_size))
2449
2450 for p, u in self._partition_updates.items():
2451 if u.tgt_group and not u.src_group:
2452 comment('Add partition %s to group %s' % (p, u.tgt_group))
2453 append('add %s %s' % (p, u.tgt_group))
2454
2455 for p, u in self._partition_updates.items():
2456 if u.tgt_size and u.src_size < u.tgt_size:
2457 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2458 append('resize %s %d' % (p, u.tgt_size))
2459
2460 for p, u in self._partition_updates.items():
2461 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2462 comment('Move partition %s from default to %s' %
2463 (p, u.tgt_group))
2464 append('move %s %s' % (p, u.tgt_group))