blob: 171c79456621a4115ce4c5e6f6b9ac724ce7fdf5 [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 Bao9dd909e2017-11-14 11:27:32 -080096# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010097AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010098 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080099
Tianjie Xu861f4132018-09-12 11:49:33 -0700100# Partitions that should have their care_map added to META/care_map.pb
101PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
102 'odm')
103
104
Tianjie Xu209db462016-05-24 17:34:52 -0700105class ErrorCode(object):
106 """Define error_codes for failures that happen during the actual
107 update package installation.
108
109 Error codes 0-999 are reserved for failures before the package
110 installation (i.e. low battery, package verification failure).
111 Detailed code in 'bootable/recovery/error_code.h' """
112
113 SYSTEM_VERIFICATION_FAILURE = 1000
114 SYSTEM_UPDATE_FAILURE = 1001
115 SYSTEM_UNEXPECTED_CONTENTS = 1002
116 SYSTEM_NONZERO_CONTENTS = 1003
117 SYSTEM_RECOVER_FAILURE = 1004
118 VENDOR_VERIFICATION_FAILURE = 2000
119 VENDOR_UPDATE_FAILURE = 2001
120 VENDOR_UNEXPECTED_CONTENTS = 2002
121 VENDOR_NONZERO_CONTENTS = 2003
122 VENDOR_RECOVER_FAILURE = 2004
123 OEM_PROP_MISMATCH = 3000
124 FINGERPRINT_MISMATCH = 3001
125 THUMBPRINT_MISMATCH = 3002
126 OLDER_BUILD = 3003
127 DEVICE_MISMATCH = 3004
128 BAD_PATCH_FILE = 3005
129 INSUFFICIENT_CACHE_SPACE = 3006
130 TUNE_PARTITION_FAILURE = 3007
131 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800132
Tao Bao80921982018-03-21 21:02:19 -0700133
Dan Albert8b72aef2015-03-23 19:13:21 -0700134class ExternalError(RuntimeError):
135 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700136
137
Tao Bao32fcdab2018-10-12 10:30:39 -0700138def InitLogging():
139 DEFAULT_LOGGING_CONFIG = {
140 'version': 1,
141 'disable_existing_loggers': False,
142 'formatters': {
143 'standard': {
144 'format':
145 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
146 'datefmt': '%Y-%m-%d %H:%M:%S',
147 },
148 },
149 'handlers': {
150 'default': {
151 'class': 'logging.StreamHandler',
152 'formatter': 'standard',
153 },
154 },
155 'loggers': {
156 '': {
157 'handlers': ['default'],
158 'level': 'WARNING',
159 'propagate': True,
160 }
161 }
162 }
163 env_config = os.getenv('LOGGING_CONFIG')
164 if env_config:
165 with open(env_config) as f:
166 config = json.load(f)
167 else:
168 config = DEFAULT_LOGGING_CONFIG
169
170 # Increase the logging level for verbose mode.
171 if OPTIONS.verbose:
172 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
173 config['loggers']['']['level'] = 'INFO'
174
175 logging.config.dictConfig(config)
176
177
Tao Bao39451582017-05-04 11:10:47 -0700178def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700179 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700180
Tao Bao73dd4f42018-10-04 16:25:33 -0700181 Args:
182 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700183 verbose: Whether the commands should be shown. Default to the global
184 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700185 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
186 stdin, etc. stdout and stderr will default to subprocess.PIPE and
187 subprocess.STDOUT respectively unless caller specifies any of them.
188
189 Returns:
190 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700191 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700192 if 'stdout' not in kwargs and 'stderr' not in kwargs:
193 kwargs['stdout'] = subprocess.PIPE
194 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700195 # Don't log any if caller explicitly says so.
196 if verbose != False:
197 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700198 return subprocess.Popen(args, **kwargs)
199
200
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800201def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800202 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800203
204 Args:
205 args: The command represented as a list of strings.
206 verbose: Whether the commands should be shown. Default to the global
207 verbosity if unspecified.
208 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
209 stdin, etc. stdout and stderr will default to subprocess.PIPE and
210 subprocess.STDOUT respectively unless caller specifies any of them.
211
Bill Peckham889b0c62019-02-21 18:53:37 -0800212 Raises:
213 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800214 """
215 proc = Run(args, verbose=verbose, **kwargs)
216 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800217
218 if proc.returncode != 0:
219 raise ExternalError(
220 "Failed to run command '{}' (exit code {})".format(
221 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800222
223
Tao Bao986ee862018-10-04 15:46:16 -0700224def RunAndCheckOutput(args, verbose=None, **kwargs):
225 """Runs the given command and returns the output.
226
227 Args:
228 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700229 verbose: Whether the commands should be shown. Default to the global
230 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700231 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
232 stdin, etc. stdout and stderr will default to subprocess.PIPE and
233 subprocess.STDOUT respectively unless caller specifies any of them.
234
235 Returns:
236 The output string.
237
238 Raises:
239 ExternalError: On non-zero exit from the command.
240 """
Tao Bao986ee862018-10-04 15:46:16 -0700241 proc = Run(args, verbose=verbose, **kwargs)
242 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700243 # Don't log any if caller explicitly says so.
244 if verbose != False:
245 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700246 if proc.returncode != 0:
247 raise ExternalError(
248 "Failed to run command '{}' (exit code {}):\n{}".format(
249 args, proc.returncode, output))
250 return output
251
252
Tao Baoc765cca2018-01-31 17:32:40 -0800253def RoundUpTo4K(value):
254 rounded_up = value + 4095
255 return rounded_up - (rounded_up % 4096)
256
257
Ying Wang7e6d4e42010-12-13 16:25:36 -0800258def CloseInheritedPipes():
259 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
260 before doing other work."""
261 if platform.system() != "Darwin":
262 return
263 for d in range(3, 1025):
264 try:
265 stat = os.fstat(d)
266 if stat is not None:
267 pipebit = stat[0] & 0x1000
268 if pipebit != 0:
269 os.close(d)
270 except OSError:
271 pass
272
273
Tao Bao410ad8b2018-08-24 12:08:38 -0700274def LoadInfoDict(input_file, repacking=False):
275 """Loads the key/value pairs from the given input target_files.
276
277 It reads `META/misc_info.txt` file in the target_files input, does sanity
278 checks and returns the parsed key/value pairs for to the given build. It's
279 usually called early when working on input target_files files, e.g. when
280 generating OTAs, or signing builds. Note that the function may be called
281 against an old target_files file (i.e. from past dessert releases). So the
282 property parsing needs to be backward compatible.
283
284 In a `META/misc_info.txt`, a few properties are stored as links to the files
285 in the PRODUCT_OUT directory. It works fine with the build system. However,
286 they are no longer available when (re)generating images from target_files zip.
287 When `repacking` is True, redirect these properties to the actual files in the
288 unzipped directory.
289
290 Args:
291 input_file: The input target_files file, which could be an open
292 zipfile.ZipFile instance, or a str for the dir that contains the files
293 unzipped from a target_files file.
294 repacking: Whether it's trying repack an target_files file after loading the
295 info dict (default: False). If so, it will rewrite a few loaded
296 properties (e.g. selinux_fc, root_dir) to point to the actual files in
297 target_files file. When doing repacking, `input_file` must be a dir.
298
299 Returns:
300 A dict that contains the parsed key/value pairs.
301
302 Raises:
303 AssertionError: On invalid input arguments.
304 ValueError: On malformed input values.
305 """
306 if repacking:
307 assert isinstance(input_file, str), \
308 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700309
Doug Zongkerc9253822014-02-04 12:17:58 -0800310 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700311 if isinstance(input_file, zipfile.ZipFile):
312 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800313 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700314 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800315 try:
316 with open(path) as f:
317 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700318 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800319 if e.errno == errno.ENOENT:
320 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800321
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700322 try:
Michael Runge6e836112014-04-15 17:40:21 -0700323 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700324 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700325 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700326
Tao Bao410ad8b2018-08-24 12:08:38 -0700327 if "recovery_api_version" not in d:
328 raise ValueError("Failed to find 'recovery_api_version'")
329 if "fstab_version" not in d:
330 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800331
Tao Bao410ad8b2018-08-24 12:08:38 -0700332 if repacking:
333 # We carry a copy of file_contexts.bin under META/. If not available, search
334 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
335 # images than the one running on device, in that case, we must have the one
336 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700337 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700338 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700339 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700340
Tom Cherryd14b8952018-08-09 14:26:00 -0700341 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700342
Tom Cherryd14b8952018-08-09 14:26:00 -0700343 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700344 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700345 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700346 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700347
Tao Baof54216f2016-03-29 15:12:37 -0700348 # Redirect {system,vendor}_base_fs_file.
349 if "system_base_fs_file" in d:
350 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700351 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700352 if os.path.exists(system_base_fs_file):
353 d["system_base_fs_file"] = system_base_fs_file
354 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700355 logger.warning(
356 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700357 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700358
359 if "vendor_base_fs_file" in d:
360 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700361 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700362 if os.path.exists(vendor_base_fs_file):
363 d["vendor_base_fs_file"] = vendor_base_fs_file
364 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700365 logger.warning(
366 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700367 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700368
Doug Zongker37974732010-09-16 17:44:38 -0700369 def makeint(key):
370 if key in d:
371 d[key] = int(d[key], 0)
372
373 makeint("recovery_api_version")
374 makeint("blocksize")
375 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700376 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700377 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700378 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700379 makeint("recovery_size")
380 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800381 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700382
Tao Baoa57ab9f2018-08-24 12:08:38 -0700383 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
384 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
385 # cases, since it may load the info_dict from an old build (e.g. when
386 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800387 system_root_image = d.get("system_root_image") == "true"
388 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700389 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700390 if isinstance(input_file, zipfile.ZipFile):
391 if recovery_fstab_path not in input_file.namelist():
392 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
393 else:
394 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
395 if not os.path.exists(path):
396 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800397 d["fstab"] = LoadRecoveryFSTab(
398 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700399
Tao Bao76def242017-11-21 09:25:31 -0800400 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700401 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700402 if isinstance(input_file, zipfile.ZipFile):
403 if recovery_fstab_path not in input_file.namelist():
404 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
405 else:
406 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
407 if not os.path.exists(path):
408 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800409 d["fstab"] = LoadRecoveryFSTab(
410 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700411
Tianjie Xucfa86222016-03-07 16:31:19 -0800412 else:
413 d["fstab"] = None
414
Tianjie Xu861f4132018-09-12 11:49:33 -0700415 # Tries to load the build props for all partitions with care_map, including
416 # system and vendor.
417 for partition in PARTITIONS_WITH_CARE_MAP:
418 d["{}.build.prop".format(partition)] = LoadBuildProp(
419 read_helper, "{}/build.prop".format(partition.upper()))
420 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800421
422 # Set up the salt (based on fingerprint or thumbprint) that will be used when
423 # adding AVB footer.
424 if d.get("avb_enable") == "true":
425 fp = None
426 if "build.prop" in d:
427 build_prop = d["build.prop"]
428 if "ro.build.fingerprint" in build_prop:
429 fp = build_prop["ro.build.fingerprint"]
430 elif "ro.build.thumbprint" in build_prop:
431 fp = build_prop["ro.build.thumbprint"]
432 if fp:
433 d["avb_salt"] = sha256(fp).hexdigest()
434
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700435 return d
436
Tao Baod1de6f32017-03-01 16:38:48 -0800437
Tao Baobcd1d162017-08-26 13:10:26 -0700438def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700439 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700440 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700441 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700442 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700443 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700444 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700445
Tao Baod1de6f32017-03-01 16:38:48 -0800446
Michael Runge6e836112014-04-15 17:40:21 -0700447def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700448 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700449 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700450 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700451 if not line or line.startswith("#"):
452 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700453 if "=" in line:
454 name, value = line.split("=", 1)
455 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700456 return d
457
Tao Baod1de6f32017-03-01 16:38:48 -0800458
Tianjie Xucfa86222016-03-07 16:31:19 -0800459def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
460 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700461 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800462 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700463 self.mount_point = mount_point
464 self.fs_type = fs_type
465 self.device = device
466 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700467 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700468
469 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800470 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700471 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700472 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700473 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700474
Tao Baod1de6f32017-03-01 16:38:48 -0800475 assert fstab_version == 2
476
477 d = {}
478 for line in data.split("\n"):
479 line = line.strip()
480 if not line or line.startswith("#"):
481 continue
482
483 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
484 pieces = line.split()
485 if len(pieces) != 5:
486 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
487
488 # Ignore entries that are managed by vold.
489 options = pieces[4]
490 if "voldmanaged=" in options:
491 continue
492
493 # It's a good line, parse it.
494 length = 0
495 options = options.split(",")
496 for i in options:
497 if i.startswith("length="):
498 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800499 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800500 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700501 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800502
Tao Baod1de6f32017-03-01 16:38:48 -0800503 mount_flags = pieces[3]
504 # Honor the SELinux context if present.
505 context = None
506 for i in mount_flags.split(","):
507 if i.startswith("context="):
508 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800509
Tao Baod1de6f32017-03-01 16:38:48 -0800510 mount_point = pieces[1]
511 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
512 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800513
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700514 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700515 # system. Other areas assume system is always at "/system" so point /system
516 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700517 if system_root_image:
518 assert not d.has_key("/system") and d.has_key("/")
519 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700520 return d
521
522
Doug Zongker37974732010-09-16 17:44:38 -0700523def DumpInfoDict(d):
524 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700525 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700526
Dan Albert8b72aef2015-03-23 19:13:21 -0700527
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800528def AppendAVBSigningArgs(cmd, partition):
529 """Append signing arguments for avbtool."""
530 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
531 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
532 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
533 if key_path and algorithm:
534 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700535 avb_salt = OPTIONS.info_dict.get("avb_salt")
536 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700537 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700538 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800539
540
Tao Bao02a08592018-07-22 12:40:45 -0700541def GetAvbChainedPartitionArg(partition, info_dict, key=None):
542 """Constructs and returns the arg to build or verify a chained partition.
543
544 Args:
545 partition: The partition name.
546 info_dict: The info dict to look up the key info and rollback index
547 location.
548 key: The key to be used for building or verifying the partition. Defaults to
549 the key listed in info_dict.
550
551 Returns:
552 A string of form "partition:rollback_index_location:key" that can be used to
553 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700554 """
555 if key is None:
556 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao2cc0ca12019-03-15 10:44:43 -0700557 pubkey_path = ExtractAvbPublicKey(key)
Tao Bao02a08592018-07-22 12:40:45 -0700558 rollback_index_location = info_dict[
559 "avb_" + partition + "_rollback_index_location"]
560 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
561
562
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700563def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800564 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700565 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700566
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700567 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800568 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
569 we are building a two-step special image (i.e. building a recovery image to
570 be loaded into /boot in two-step OTAs).
571
572 Return the image data, or None if sourcedir does not appear to contains files
573 for building the requested image.
574 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700575
576 def make_ramdisk():
577 ramdisk_img = tempfile.NamedTemporaryFile()
578
579 if os.access(fs_config_file, os.F_OK):
580 cmd = ["mkbootfs", "-f", fs_config_file,
581 os.path.join(sourcedir, "RAMDISK")]
582 else:
583 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
584 p1 = Run(cmd, stdout=subprocess.PIPE)
585 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
586
587 p2.wait()
588 p1.wait()
589 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
590 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
591
592 return ramdisk_img
593
594 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
595 return None
596
597 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700598 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700599
Doug Zongkerd5131602012-08-02 14:46:42 -0700600 if info_dict is None:
601 info_dict = OPTIONS.info_dict
602
Doug Zongkereef39442009-04-02 12:14:19 -0700603 img = tempfile.NamedTemporaryFile()
604
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700605 if has_ramdisk:
606 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700607
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800608 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
609 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
610
611 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700612
Benoit Fradina45a8682014-07-14 21:00:43 +0200613 fn = os.path.join(sourcedir, "second")
614 if os.access(fn, os.F_OK):
615 cmd.append("--second")
616 cmd.append(fn)
617
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800618 fn = os.path.join(sourcedir, "dtb")
619 if os.access(fn, os.F_OK):
620 cmd.append("--dtb")
621 cmd.append(fn)
622
Doug Zongker171f1cd2009-06-15 22:36:37 -0700623 fn = os.path.join(sourcedir, "cmdline")
624 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700625 cmd.append("--cmdline")
626 cmd.append(open(fn).read().rstrip("\n"))
627
628 fn = os.path.join(sourcedir, "base")
629 if os.access(fn, os.F_OK):
630 cmd.append("--base")
631 cmd.append(open(fn).read().rstrip("\n"))
632
Ying Wang4de6b5b2010-08-25 14:29:34 -0700633 fn = os.path.join(sourcedir, "pagesize")
634 if os.access(fn, os.F_OK):
635 cmd.append("--pagesize")
636 cmd.append(open(fn).read().rstrip("\n"))
637
Tao Bao76def242017-11-21 09:25:31 -0800638 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700639 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700640 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700641
Tao Bao76def242017-11-21 09:25:31 -0800642 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000643 if args and args.strip():
644 cmd.extend(shlex.split(args))
645
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700646 if has_ramdisk:
647 cmd.extend(["--ramdisk", ramdisk_img.name])
648
Tao Baod95e9fd2015-03-29 23:07:41 -0700649 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800650 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700651 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700652 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700653 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700654 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700655
Tao Baobf70c3182017-07-11 17:27:55 -0700656 # "boot" or "recovery", without extension.
657 partition_name = os.path.basename(sourcedir).lower()
658
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800659 if partition_name == "recovery":
660 if info_dict.get("include_recovery_dtbo") == "true":
661 fn = os.path.join(sourcedir, "recovery_dtbo")
662 cmd.extend(["--recovery_dtbo", fn])
663 if info_dict.get("include_recovery_acpio") == "true":
664 fn = os.path.join(sourcedir, "recovery_acpio")
665 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700666
Tao Bao986ee862018-10-04 15:46:16 -0700667 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700668
Tao Bao76def242017-11-21 09:25:31 -0800669 if (info_dict.get("boot_signer") == "true" and
670 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800671 # Hard-code the path as "/boot" for two-step special recovery image (which
672 # will be loaded into /boot during the two-step OTA).
673 if two_step_image:
674 path = "/boot"
675 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700676 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700677 cmd = [OPTIONS.boot_signer_path]
678 cmd.extend(OPTIONS.boot_signer_args)
679 cmd.extend([path, img.name,
680 info_dict["verity_key"] + ".pk8",
681 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700682 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700683
Tao Baod95e9fd2015-03-29 23:07:41 -0700684 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800685 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700686 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700687 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800688 # We have switched from the prebuilt futility binary to using the tool
689 # (futility-host) built from the source. Override the setting in the old
690 # TF.zip.
691 futility = info_dict["futility"]
692 if futility.startswith("prebuilts/"):
693 futility = "futility-host"
694 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700695 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700696 info_dict["vboot_key"] + ".vbprivk",
697 info_dict["vboot_subkey"] + ".vbprivk",
698 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700699 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700700 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700701
Tao Baof3282b42015-04-01 11:21:55 -0700702 # Clean up the temp files.
703 img_unsigned.close()
704 img_keyblock.close()
705
David Zeuthen8fecb282017-12-01 16:24:01 -0500706 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800707 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700708 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500709 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400710 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700711 "--partition_size", str(part_size), "--partition_name",
712 partition_name]
713 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500714 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400715 if args and args.strip():
716 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700717 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500718
719 img.seek(os.SEEK_SET, 0)
720 data = img.read()
721
722 if has_ramdisk:
723 ramdisk_img.close()
724 img.close()
725
726 return data
727
728
Doug Zongkerd5131602012-08-02 14:46:42 -0700729def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800730 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700731 """Return a File object with the desired bootable image.
732
733 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
734 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
735 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700736
Doug Zongker55d93282011-01-25 17:03:34 -0800737 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
738 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700739 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800740 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700741
742 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
743 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700744 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700745 return File.FromLocalFile(name, prebuilt_path)
746
Tao Bao32fcdab2018-10-12 10:30:39 -0700747 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700748
749 if info_dict is None:
750 info_dict = OPTIONS.info_dict
751
752 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800753 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
754 # for recovery.
755 has_ramdisk = (info_dict.get("system_root_image") != "true" or
756 prebuilt_name != "boot.img" or
757 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700758
Doug Zongker6f1d0312014-08-22 08:07:12 -0700759 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400760 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
761 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800762 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700763 if data:
764 return File(name, data)
765 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800766
Doug Zongkereef39442009-04-02 12:14:19 -0700767
Narayan Kamatha07bf042017-08-14 14:49:21 +0100768def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800769 """Gunzips the given gzip compressed file to a given output file."""
770 with gzip.open(in_filename, "rb") as in_file, \
771 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100772 shutil.copyfileobj(in_file, out_file)
773
774
Tao Bao0ff15de2019-03-20 11:26:06 -0700775def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800776 """Unzips the archive to the given directory.
777
778 Args:
779 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800780 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700781 patterns: Files to unzip from the archive. If omitted, will unzip the entire
782 archvie. Non-matching patterns will be filtered out. If there's no match
783 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800784 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800785 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700786 if patterns is not None:
787 # Filter out non-matching patterns. unzip will complain otherwise.
788 with zipfile.ZipFile(filename) as input_zip:
789 names = input_zip.namelist()
790 filtered = [
791 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
792
793 # There isn't any matching files. Don't unzip anything.
794 if not filtered:
795 return
796 cmd.extend(filtered)
797
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800798 RunAndCheckOutput(cmd)
799
800
Doug Zongker75f17362009-12-08 13:46:44 -0800801def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800802 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800803
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800804 Args:
805 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
806 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
807
808 pattern: Files to unzip from the archive. If omitted, will unzip the entire
809 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800810
Tao Bao1c830bf2017-12-25 10:43:47 -0800811 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800812 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800813 """
Doug Zongkereef39442009-04-02 12:14:19 -0700814
Tao Bao1c830bf2017-12-25 10:43:47 -0800815 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800816 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
817 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800818 UnzipToDir(m.group(1), tmp, pattern)
819 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800820 filename = m.group(1)
821 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800822 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800823
Tao Baodba59ee2018-01-09 13:21:02 -0800824 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700825
826
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700827def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
828 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800829 """Returns a SparseImage object suitable for passing to BlockImageDiff.
830
831 This function loads the specified sparse image from the given path, and
832 performs additional processing for OTA purpose. For example, it always adds
833 block 0 to clobbered blocks list. It also detects files that cannot be
834 reconstructed from the block list, for whom we should avoid applying imgdiff.
835
836 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700837 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800838 tmpdir: The directory that contains the prebuilt image and block map file.
839 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800840 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700841 hashtree_info_generator: If present, generates the hashtree_info for this
842 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800843 Returns:
844 A SparseImage object, with file_map info loaded.
845 """
Tao Baoc765cca2018-01-31 17:32:40 -0800846 path = os.path.join(tmpdir, "IMAGES", which + ".img")
847 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
848
849 # The image and map files must have been created prior to calling
850 # ota_from_target_files.py (since LMP).
851 assert os.path.exists(path) and os.path.exists(mappath)
852
853 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
854 # it to clobbered_blocks so that it will be written to the target
855 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
856 clobbered_blocks = "0"
857
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700858 image = sparse_img.SparseImage(
859 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
860 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800861
862 # block.map may contain less blocks, because mke2fs may skip allocating blocks
863 # if they contain all zeros. We can't reconstruct such a file from its block
864 # list. Tag such entries accordingly. (Bug: 65213616)
865 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800866 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700867 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800868 continue
869
Tom Cherryd14b8952018-08-09 14:26:00 -0700870 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
871 # filename listed in system.map may contain an additional leading slash
872 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
873 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700874 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
875
Tom Cherryd14b8952018-08-09 14:26:00 -0700876 # Special handling another case, where files not under /system
877 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700878 if which == 'system' and not arcname.startswith('SYSTEM'):
879 arcname = 'ROOT/' + arcname
880
881 assert arcname in input_zip.namelist(), \
882 "Failed to find the ZIP entry for {}".format(entry)
883
Tao Baoc765cca2018-01-31 17:32:40 -0800884 info = input_zip.getinfo(arcname)
885 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800886
887 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800888 # image, check the original block list to determine its completeness. Note
889 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800890 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800891 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800892
Tao Baoc765cca2018-01-31 17:32:40 -0800893 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
894 ranges.extra['incomplete'] = True
895
896 return image
897
898
Doug Zongkereef39442009-04-02 12:14:19 -0700899def GetKeyPasswords(keylist):
900 """Given a list of keys, prompt the user to enter passwords for
901 those which require them. Return a {key: password} dict. password
902 will be None if the key has no password."""
903
Doug Zongker8ce7c252009-05-22 13:34:54 -0700904 no_passwords = []
905 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700906 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700907 devnull = open("/dev/null", "w+b")
908 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800909 # We don't need a password for things that aren't really keys.
910 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700911 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700912 continue
913
T.R. Fullhart37e10522013-03-18 10:31:26 -0700914 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700915 "-inform", "DER", "-nocrypt"],
916 stdin=devnull.fileno(),
917 stdout=devnull.fileno(),
918 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700919 p.communicate()
920 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700921 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700922 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700923 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700924 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
925 "-inform", "DER", "-passin", "pass:"],
926 stdin=devnull.fileno(),
927 stdout=devnull.fileno(),
928 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700929 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700930 if p.returncode == 0:
931 # Encrypted key with empty string as password.
932 key_passwords[k] = ''
933 elif stderr.startswith('Error decrypting key'):
934 # Definitely encrypted key.
935 # It would have said "Error reading key" if it didn't parse correctly.
936 need_passwords.append(k)
937 else:
938 # Potentially, a type of key that openssl doesn't understand.
939 # We'll let the routines in signapk.jar handle it.
940 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700941 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700942
T.R. Fullhart37e10522013-03-18 10:31:26 -0700943 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800944 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700945 return key_passwords
946
947
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800948def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700949 """Gets the minSdkVersion declared in the APK.
950
951 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
952 This can be both a decimal number (API Level) or a codename.
953
954 Args:
955 apk_name: The APK filename.
956
957 Returns:
958 The parsed SDK version string.
959
960 Raises:
961 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800962 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700963 proc = Run(
964 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
965 stderr=subprocess.PIPE)
966 stdoutdata, stderrdata = proc.communicate()
967 if proc.returncode != 0:
968 raise ExternalError(
969 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
970 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800971
Tao Baof47bf0f2018-03-21 23:28:51 -0700972 for line in stdoutdata.split("\n"):
973 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800974 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
975 if m:
976 return m.group(1)
977 raise ExternalError("No minSdkVersion returned by aapt")
978
979
980def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700981 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800982
Tao Baof47bf0f2018-03-21 23:28:51 -0700983 If minSdkVersion is set to a codename, it is translated to a number using the
984 provided map.
985
986 Args:
987 apk_name: The APK filename.
988
989 Returns:
990 The parsed SDK version number.
991
992 Raises:
993 ExternalError: On failing to get the min SDK version number.
994 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800995 version = GetMinSdkVersion(apk_name)
996 try:
997 return int(version)
998 except ValueError:
999 # Not a decimal number. Codename?
1000 if version in codename_to_api_level_map:
1001 return codename_to_api_level_map[version]
1002 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001003 raise ExternalError(
1004 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1005 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001006
1007
1008def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001009 codename_to_api_level_map=None, whole_file=False,
1010 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001011 """Sign the input_name zip/jar/apk, producing output_name. Use the
1012 given key and password (the latter may be None if the key does not
1013 have a password.
1014
Doug Zongker951495f2009-08-14 12:44:19 -07001015 If whole_file is true, use the "-w" option to SignApk to embed a
1016 signature that covers the whole file in the archive comment of the
1017 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001018
1019 min_api_level is the API Level (int) of the oldest platform this file may end
1020 up on. If not specified for an APK, the API Level is obtained by interpreting
1021 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1022
1023 codename_to_api_level_map is needed to translate the codename which may be
1024 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001025
1026 Caller may optionally specify extra args to be passed to SignApk, which
1027 defaults to OPTIONS.extra_signapk_args if omitted.
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 = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001031 if extra_signapk_args is None:
1032 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001033
Alex Klyubin9667b182015-12-10 13:38:50 -08001034 java_library_path = os.path.join(
1035 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1036
Tao Baoe95540e2016-11-08 12:08:53 -08001037 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1038 ["-Djava.library.path=" + java_library_path,
1039 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001040 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001041 if whole_file:
1042 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001043
1044 min_sdk_version = min_api_level
1045 if min_sdk_version is None:
1046 if not whole_file:
1047 min_sdk_version = GetMinSdkVersionInt(
1048 input_name, codename_to_api_level_map)
1049 if min_sdk_version is not None:
1050 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1051
T.R. Fullhart37e10522013-03-18 10:31:26 -07001052 cmd.extend([key + OPTIONS.public_key_suffix,
1053 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001054 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001055
Tao Bao73dd4f42018-10-04 16:25:33 -07001056 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001057 if password is not None:
1058 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001059 stdoutdata, _ = proc.communicate(password)
1060 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001061 raise ExternalError(
1062 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001063 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001064
Doug Zongkereef39442009-04-02 12:14:19 -07001065
Doug Zongker37974732010-09-16 17:44:38 -07001066def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001067 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001068
Tao Bao9dd909e2017-11-14 11:27:32 -08001069 For non-AVB images, raise exception if the data is too big. Print a warning
1070 if the data is nearing the maximum size.
1071
1072 For AVB images, the actual image size should be identical to the limit.
1073
1074 Args:
1075 data: A string that contains all the data for the partition.
1076 target: The partition name. The ".img" suffix is optional.
1077 info_dict: The dict to be looked up for relevant info.
1078 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001079 if target.endswith(".img"):
1080 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001081 mount_point = "/" + target
1082
Ying Wangf8824af2014-06-03 14:07:27 -07001083 fs_type = None
1084 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001085 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001086 if mount_point == "/userdata":
1087 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001088 p = info_dict["fstab"][mount_point]
1089 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001090 device = p.device
1091 if "/" in device:
1092 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001093 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001094 if not fs_type or not limit:
1095 return
Doug Zongkereef39442009-04-02 12:14:19 -07001096
Andrew Boie0f9aec82012-02-14 09:32:52 -08001097 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001098 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1099 # path.
1100 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1101 if size != limit:
1102 raise ExternalError(
1103 "Mismatching image size for %s: expected %d actual %d" % (
1104 target, limit, size))
1105 else:
1106 pct = float(size) * 100.0 / limit
1107 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1108 if pct >= 99.0:
1109 raise ExternalError(msg)
1110 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001111 logger.warning("\n WARNING: %s\n", msg)
1112 else:
1113 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001114
1115
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001116def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001117 """Parses the APK certs info from a given target-files zip.
1118
1119 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1120 tuple with the following elements: (1) a dictionary that maps packages to
1121 certs (based on the "certificate" and "private_key" attributes in the file;
1122 (2) a string representing the extension of compressed APKs in the target files
1123 (e.g ".gz", ".bro").
1124
1125 Args:
1126 tf_zip: The input target_files ZipFile (already open).
1127
1128 Returns:
1129 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1130 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1131 no compressed APKs.
1132 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001133 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001134 compressed_extension = None
1135
Tao Bao0f990332017-09-08 19:02:54 -07001136 # META/apkcerts.txt contains the info for _all_ the packages known at build
1137 # time. Filter out the ones that are not installed.
1138 installed_files = set()
1139 for name in tf_zip.namelist():
1140 basename = os.path.basename(name)
1141 if basename:
1142 installed_files.add(basename)
1143
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001144 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1145 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001146 if not line:
1147 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001148 m = re.match(
1149 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1150 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1151 line)
1152 if not m:
1153 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001154
Tao Bao818ddf52018-01-05 11:17:34 -08001155 matches = m.groupdict()
1156 cert = matches["CERT"]
1157 privkey = matches["PRIVKEY"]
1158 name = matches["NAME"]
1159 this_compressed_extension = matches["COMPRESSED"]
1160
1161 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1162 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1163 if cert in SPECIAL_CERT_STRINGS and not privkey:
1164 certmap[name] = cert
1165 elif (cert.endswith(OPTIONS.public_key_suffix) and
1166 privkey.endswith(OPTIONS.private_key_suffix) and
1167 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1168 certmap[name] = cert[:-public_key_suffix_len]
1169 else:
1170 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1171
1172 if not this_compressed_extension:
1173 continue
1174
1175 # Only count the installed files.
1176 filename = name + '.' + this_compressed_extension
1177 if filename not in installed_files:
1178 continue
1179
1180 # Make sure that all the values in the compression map have the same
1181 # extension. We don't support multiple compression methods in the same
1182 # system image.
1183 if compressed_extension:
1184 if this_compressed_extension != compressed_extension:
1185 raise ValueError(
1186 "Multiple compressed extensions: {} vs {}".format(
1187 compressed_extension, this_compressed_extension))
1188 else:
1189 compressed_extension = this_compressed_extension
1190
1191 return (certmap,
1192 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001193
1194
Doug Zongkereef39442009-04-02 12:14:19 -07001195COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001196Global options
1197
1198 -p (--path) <dir>
1199 Prepend <dir>/bin to the list of places to search for binaries run by this
1200 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001201
Doug Zongker05d3dea2009-06-22 11:32:31 -07001202 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001203 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001204
Tao Bao30df8b42018-04-23 15:32:53 -07001205 -x (--extra) <key=value>
1206 Add a key/value pair to the 'extras' dict, which device-specific extension
1207 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001208
Doug Zongkereef39442009-04-02 12:14:19 -07001209 -v (--verbose)
1210 Show command lines being executed.
1211
1212 -h (--help)
1213 Display this usage message and exit.
1214"""
1215
1216def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001217 print(docstring.rstrip("\n"))
1218 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001219
1220
1221def ParseOptions(argv,
1222 docstring,
1223 extra_opts="", extra_long_opts=(),
1224 extra_option_handler=None):
1225 """Parse the options in argv and return any arguments that aren't
1226 flags. docstring is the calling module's docstring, to be displayed
1227 for errors and -h. extra_opts and extra_long_opts are for flags
1228 defined by the caller, which are processed by passing them to
1229 extra_option_handler."""
1230
1231 try:
1232 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001233 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001234 ["help", "verbose", "path=", "signapk_path=",
1235 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001236 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001237 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1238 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001239 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001240 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001241 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001242 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001243 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001244 sys.exit(2)
1245
Doug Zongkereef39442009-04-02 12:14:19 -07001246 for o, a in opts:
1247 if o in ("-h", "--help"):
1248 Usage(docstring)
1249 sys.exit()
1250 elif o in ("-v", "--verbose"):
1251 OPTIONS.verbose = True
1252 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001253 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001254 elif o in ("--signapk_path",):
1255 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001256 elif o in ("--signapk_shared_library_path",):
1257 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001258 elif o in ("--extra_signapk_args",):
1259 OPTIONS.extra_signapk_args = shlex.split(a)
1260 elif o in ("--java_path",):
1261 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001262 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001263 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001264 elif o in ("--public_key_suffix",):
1265 OPTIONS.public_key_suffix = a
1266 elif o in ("--private_key_suffix",):
1267 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001268 elif o in ("--boot_signer_path",):
1269 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001270 elif o in ("--boot_signer_args",):
1271 OPTIONS.boot_signer_args = shlex.split(a)
1272 elif o in ("--verity_signer_path",):
1273 OPTIONS.verity_signer_path = a
1274 elif o in ("--verity_signer_args",):
1275 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001276 elif o in ("-s", "--device_specific"):
1277 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001278 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001279 key, value = a.split("=", 1)
1280 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001281 else:
1282 if extra_option_handler is None or not extra_option_handler(o, a):
1283 assert False, "unknown option \"%s\"" % (o,)
1284
Doug Zongker85448772014-09-09 14:59:20 -07001285 if OPTIONS.search_path:
1286 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1287 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001288
1289 return args
1290
1291
Tao Bao4c851b12016-09-19 13:54:38 -07001292def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001293 """Make a temp file and add it to the list of things to be deleted
1294 when Cleanup() is called. Return the filename."""
1295 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1296 os.close(fd)
1297 OPTIONS.tempfiles.append(fn)
1298 return fn
1299
1300
Tao Bao1c830bf2017-12-25 10:43:47 -08001301def MakeTempDir(prefix='tmp', suffix=''):
1302 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1303
1304 Returns:
1305 The absolute pathname of the new directory.
1306 """
1307 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1308 OPTIONS.tempfiles.append(dir_name)
1309 return dir_name
1310
1311
Doug Zongkereef39442009-04-02 12:14:19 -07001312def Cleanup():
1313 for i in OPTIONS.tempfiles:
1314 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001315 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001316 else:
1317 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001318 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001319
1320
1321class PasswordManager(object):
1322 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001323 self.editor = os.getenv("EDITOR")
1324 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001325
1326 def GetPasswords(self, items):
1327 """Get passwords corresponding to each string in 'items',
1328 returning a dict. (The dict may have keys in addition to the
1329 values in 'items'.)
1330
1331 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1332 user edit that file to add more needed passwords. If no editor is
1333 available, or $ANDROID_PW_FILE isn't define, prompts the user
1334 interactively in the ordinary way.
1335 """
1336
1337 current = self.ReadFile()
1338
1339 first = True
1340 while True:
1341 missing = []
1342 for i in items:
1343 if i not in current or not current[i]:
1344 missing.append(i)
1345 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001346 if not missing:
1347 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001348
1349 for i in missing:
1350 current[i] = ""
1351
1352 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001353 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001354 answer = raw_input("try to edit again? [y]> ").strip()
1355 if answer and answer[0] not in 'yY':
1356 raise RuntimeError("key passwords unavailable")
1357 first = False
1358
1359 current = self.UpdateAndReadFile(current)
1360
Dan Albert8b72aef2015-03-23 19:13:21 -07001361 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001362 """Prompt the user to enter a value (password) for each key in
1363 'current' whose value is fales. Returns a new dict with all the
1364 values.
1365 """
1366 result = {}
1367 for k, v in sorted(current.iteritems()):
1368 if v:
1369 result[k] = v
1370 else:
1371 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001372 result[k] = getpass.getpass(
1373 "Enter password for %s key> " % k).strip()
1374 if result[k]:
1375 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001376 return result
1377
1378 def UpdateAndReadFile(self, current):
1379 if not self.editor or not self.pwfile:
1380 return self.PromptResult(current)
1381
1382 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001383 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001384 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1385 f.write("# (Additional spaces are harmless.)\n\n")
1386
1387 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001388 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1389 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001390 f.write("[[[ %s ]]] %s\n" % (v, k))
1391 if not v and first_line is None:
1392 # position cursor on first line with no password.
1393 first_line = i + 4
1394 f.close()
1395
Tao Bao986ee862018-10-04 15:46:16 -07001396 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001397
1398 return self.ReadFile()
1399
1400 def ReadFile(self):
1401 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001402 if self.pwfile is None:
1403 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001404 try:
1405 f = open(self.pwfile, "r")
1406 for line in f:
1407 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001408 if not line or line[0] == '#':
1409 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001410 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1411 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001412 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001413 else:
1414 result[m.group(2)] = m.group(1)
1415 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001416 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001417 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001418 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001419 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001420
1421
Dan Albert8e0178d2015-01-27 15:53:15 -08001422def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1423 compress_type=None):
1424 import datetime
1425
1426 # http://b/18015246
1427 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1428 # for files larger than 2GiB. We can work around this by adjusting their
1429 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1430 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1431 # it isn't clear to me exactly what circumstances cause this).
1432 # `zipfile.write()` must be used directly to work around this.
1433 #
1434 # This mess can be avoided if we port to python3.
1435 saved_zip64_limit = zipfile.ZIP64_LIMIT
1436 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1437
1438 if compress_type is None:
1439 compress_type = zip_file.compression
1440 if arcname is None:
1441 arcname = filename
1442
1443 saved_stat = os.stat(filename)
1444
1445 try:
1446 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1447 # file to be zipped and reset it when we're done.
1448 os.chmod(filename, perms)
1449
1450 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001451 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1452 # intentional. zip stores datetimes in local time without a time zone
1453 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1454 # in the zip archive.
1455 local_epoch = datetime.datetime.fromtimestamp(0)
1456 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001457 os.utime(filename, (timestamp, timestamp))
1458
1459 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1460 finally:
1461 os.chmod(filename, saved_stat.st_mode)
1462 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1463 zipfile.ZIP64_LIMIT = saved_zip64_limit
1464
1465
Tao Bao58c1b962015-05-20 09:32:18 -07001466def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001467 compress_type=None):
1468 """Wrap zipfile.writestr() function to work around the zip64 limit.
1469
1470 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1471 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1472 when calling crc32(bytes).
1473
1474 But it still works fine to write a shorter string into a large zip file.
1475 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1476 when we know the string won't be too long.
1477 """
1478
1479 saved_zip64_limit = zipfile.ZIP64_LIMIT
1480 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1481
1482 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1483 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001484 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001485 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001486 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001487 else:
Tao Baof3282b42015-04-01 11:21:55 -07001488 zinfo = zinfo_or_arcname
1489
1490 # If compress_type is given, it overrides the value in zinfo.
1491 if compress_type is not None:
1492 zinfo.compress_type = compress_type
1493
Tao Bao58c1b962015-05-20 09:32:18 -07001494 # If perms is given, it has a priority.
1495 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001496 # If perms doesn't set the file type, mark it as a regular file.
1497 if perms & 0o770000 == 0:
1498 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001499 zinfo.external_attr = perms << 16
1500
Tao Baof3282b42015-04-01 11:21:55 -07001501 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001502 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1503
Dan Albert8b72aef2015-03-23 19:13:21 -07001504 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001505 zipfile.ZIP64_LIMIT = saved_zip64_limit
1506
1507
Tao Bao89d7ab22017-12-14 17:05:33 -08001508def ZipDelete(zip_filename, entries):
1509 """Deletes entries from a ZIP file.
1510
1511 Since deleting entries from a ZIP file is not supported, it shells out to
1512 'zip -d'.
1513
1514 Args:
1515 zip_filename: The name of the ZIP file.
1516 entries: The name of the entry, or the list of names to be deleted.
1517
1518 Raises:
1519 AssertionError: In case of non-zero return from 'zip'.
1520 """
1521 if isinstance(entries, basestring):
1522 entries = [entries]
1523 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001524 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001525
1526
Tao Baof3282b42015-04-01 11:21:55 -07001527def ZipClose(zip_file):
1528 # http://b/18015246
1529 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1530 # central directory.
1531 saved_zip64_limit = zipfile.ZIP64_LIMIT
1532 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1533
1534 zip_file.close()
1535
1536 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001537
1538
1539class DeviceSpecificParams(object):
1540 module = None
1541 def __init__(self, **kwargs):
1542 """Keyword arguments to the constructor become attributes of this
1543 object, which is passed to all functions in the device-specific
1544 module."""
1545 for k, v in kwargs.iteritems():
1546 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001547 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001548
1549 if self.module is None:
1550 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001551 if not path:
1552 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001553 try:
1554 if os.path.isdir(path):
1555 info = imp.find_module("releasetools", [path])
1556 else:
1557 d, f = os.path.split(path)
1558 b, x = os.path.splitext(f)
1559 if x == ".py":
1560 f = b
1561 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001562 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001563 self.module = imp.load_module("device_specific", *info)
1564 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001565 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001566
1567 def _DoCall(self, function_name, *args, **kwargs):
1568 """Call the named function in the device-specific module, passing
1569 the given args and kwargs. The first argument to the call will be
1570 the DeviceSpecific object itself. If there is no module, or the
1571 module does not define the function, return the value of the
1572 'default' kwarg (which itself defaults to None)."""
1573 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001574 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001575 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1576
1577 def FullOTA_Assertions(self):
1578 """Called after emitting the block of assertions at the top of a
1579 full OTA package. Implementations can add whatever additional
1580 assertions they like."""
1581 return self._DoCall("FullOTA_Assertions")
1582
Doug Zongkere5ff5902012-01-17 10:55:37 -08001583 def FullOTA_InstallBegin(self):
1584 """Called at the start of full OTA installation."""
1585 return self._DoCall("FullOTA_InstallBegin")
1586
Yifan Hong10c530d2018-12-27 17:34:18 -08001587 def FullOTA_GetBlockDifferences(self):
1588 """Called during full OTA installation and verification.
1589 Implementation should return a list of BlockDifference objects describing
1590 the update on each additional partitions.
1591 """
1592 return self._DoCall("FullOTA_GetBlockDifferences")
1593
Doug Zongker05d3dea2009-06-22 11:32:31 -07001594 def FullOTA_InstallEnd(self):
1595 """Called at the end of full OTA installation; typically this is
1596 used to install the image for the device's baseband processor."""
1597 return self._DoCall("FullOTA_InstallEnd")
1598
1599 def IncrementalOTA_Assertions(self):
1600 """Called after emitting the block of assertions at the top of an
1601 incremental OTA package. Implementations can add whatever
1602 additional assertions they like."""
1603 return self._DoCall("IncrementalOTA_Assertions")
1604
Doug Zongkere5ff5902012-01-17 10:55:37 -08001605 def IncrementalOTA_VerifyBegin(self):
1606 """Called at the start of the verification phase of incremental
1607 OTA installation; additional checks can be placed here to abort
1608 the script before any changes are made."""
1609 return self._DoCall("IncrementalOTA_VerifyBegin")
1610
Doug Zongker05d3dea2009-06-22 11:32:31 -07001611 def IncrementalOTA_VerifyEnd(self):
1612 """Called at the end of the verification phase of incremental OTA
1613 installation; additional checks can be placed here to abort the
1614 script before any changes are made."""
1615 return self._DoCall("IncrementalOTA_VerifyEnd")
1616
Doug Zongkere5ff5902012-01-17 10:55:37 -08001617 def IncrementalOTA_InstallBegin(self):
1618 """Called at the start of incremental OTA installation (after
1619 verification is complete)."""
1620 return self._DoCall("IncrementalOTA_InstallBegin")
1621
Yifan Hong10c530d2018-12-27 17:34:18 -08001622 def IncrementalOTA_GetBlockDifferences(self):
1623 """Called during incremental OTA installation and verification.
1624 Implementation should return a list of BlockDifference objects describing
1625 the update on each additional partitions.
1626 """
1627 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1628
Doug Zongker05d3dea2009-06-22 11:32:31 -07001629 def IncrementalOTA_InstallEnd(self):
1630 """Called at the end of incremental OTA installation; typically
1631 this is used to install the image for the device's baseband
1632 processor."""
1633 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001634
Tao Bao9bc6bb22015-11-09 16:58:28 -08001635 def VerifyOTA_Assertions(self):
1636 return self._DoCall("VerifyOTA_Assertions")
1637
Tao Bao76def242017-11-21 09:25:31 -08001638
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001639class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001640 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001641 self.name = name
1642 self.data = data
1643 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001644 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001645 self.sha1 = sha1(data).hexdigest()
1646
1647 @classmethod
1648 def FromLocalFile(cls, name, diskname):
1649 f = open(diskname, "rb")
1650 data = f.read()
1651 f.close()
1652 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001653
1654 def WriteToTemp(self):
1655 t = tempfile.NamedTemporaryFile()
1656 t.write(self.data)
1657 t.flush()
1658 return t
1659
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001660 def WriteToDir(self, d):
1661 with open(os.path.join(d, self.name), "wb") as fp:
1662 fp.write(self.data)
1663
Geremy Condra36bd3652014-02-06 19:45:10 -08001664 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001665 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001666
Tao Bao76def242017-11-21 09:25:31 -08001667
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001668DIFF_PROGRAM_BY_EXT = {
1669 ".gz" : "imgdiff",
1670 ".zip" : ["imgdiff", "-z"],
1671 ".jar" : ["imgdiff", "-z"],
1672 ".apk" : ["imgdiff", "-z"],
1673 ".img" : "imgdiff",
1674 }
1675
Tao Bao76def242017-11-21 09:25:31 -08001676
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001677class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001678 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001679 self.tf = tf
1680 self.sf = sf
1681 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001682 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001683
1684 def ComputePatch(self):
1685 """Compute the patch (as a string of data) needed to turn sf into
1686 tf. Returns the same tuple as GetPatch()."""
1687
1688 tf = self.tf
1689 sf = self.sf
1690
Doug Zongker24cd2802012-08-14 16:36:15 -07001691 if self.diff_program:
1692 diff_program = self.diff_program
1693 else:
1694 ext = os.path.splitext(tf.name)[1]
1695 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001696
1697 ttemp = tf.WriteToTemp()
1698 stemp = sf.WriteToTemp()
1699
1700 ext = os.path.splitext(tf.name)[1]
1701
1702 try:
1703 ptemp = tempfile.NamedTemporaryFile()
1704 if isinstance(diff_program, list):
1705 cmd = copy.copy(diff_program)
1706 else:
1707 cmd = [diff_program]
1708 cmd.append(stemp.name)
1709 cmd.append(ttemp.name)
1710 cmd.append(ptemp.name)
1711 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001712 err = []
1713 def run():
1714 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001715 if e:
1716 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001717 th = threading.Thread(target=run)
1718 th.start()
1719 th.join(timeout=300) # 5 mins
1720 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001721 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001722 p.terminate()
1723 th.join(5)
1724 if th.is_alive():
1725 p.kill()
1726 th.join()
1727
Tianjie Xua2a9f992018-01-05 15:15:54 -08001728 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001729 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001730 self.patch = None
1731 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001732 diff = ptemp.read()
1733 finally:
1734 ptemp.close()
1735 stemp.close()
1736 ttemp.close()
1737
1738 self.patch = diff
1739 return self.tf, self.sf, self.patch
1740
1741
1742 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001743 """Returns a tuple of (target_file, source_file, patch_data).
1744
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001745 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001746 computing the patch failed.
1747 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001748 return self.tf, self.sf, self.patch
1749
1750
1751def ComputeDifferences(diffs):
1752 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001753 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001754
1755 # Do the largest files first, to try and reduce the long-pole effect.
1756 by_size = [(i.tf.size, i) for i in diffs]
1757 by_size.sort(reverse=True)
1758 by_size = [i[1] for i in by_size]
1759
1760 lock = threading.Lock()
1761 diff_iter = iter(by_size) # accessed under lock
1762
1763 def worker():
1764 try:
1765 lock.acquire()
1766 for d in diff_iter:
1767 lock.release()
1768 start = time.time()
1769 d.ComputePatch()
1770 dur = time.time() - start
1771 lock.acquire()
1772
1773 tf, sf, patch = d.GetPatch()
1774 if sf.name == tf.name:
1775 name = tf.name
1776 else:
1777 name = "%s (%s)" % (tf.name, sf.name)
1778 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001779 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001780 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001781 logger.info(
1782 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1783 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001784 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001785 except Exception:
1786 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001787 raise
1788
1789 # start worker threads; wait for them all to finish.
1790 threads = [threading.Thread(target=worker)
1791 for i in range(OPTIONS.worker_threads)]
1792 for th in threads:
1793 th.start()
1794 while threads:
1795 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001796
1797
Dan Albert8b72aef2015-03-23 19:13:21 -07001798class BlockDifference(object):
1799 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001800 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001801 self.tgt = tgt
1802 self.src = src
1803 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001804 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001805 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001806
Tao Baodd2a5892015-03-12 12:32:37 -07001807 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001808 version = max(
1809 int(i) for i in
1810 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001811 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001812 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001813
1814 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001815 version=self.version,
1816 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001817 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001818 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001819 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001820 self.touched_src_ranges = b.touched_src_ranges
1821 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001822
Yifan Hong10c530d2018-12-27 17:34:18 -08001823 # On devices with dynamic partitions, for new partitions,
1824 # src is None but OPTIONS.source_info_dict is not.
1825 if OPTIONS.source_info_dict is None:
1826 is_dynamic_build = OPTIONS.info_dict.get(
1827 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001828 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001829 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001830 is_dynamic_build = OPTIONS.source_info_dict.get(
1831 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001832 is_dynamic_source = partition in shlex.split(
1833 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001834
Yifan Hongbb2658d2019-01-25 12:30:58 -08001835 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001836 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1837
Yifan Hongbb2658d2019-01-25 12:30:58 -08001838 # For dynamic partitions builds, check partition list in both source
1839 # and target build because new partitions may be added, and existing
1840 # partitions may be removed.
1841 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1842
Yifan Hong10c530d2018-12-27 17:34:18 -08001843 if is_dynamic:
1844 self.device = 'map_partition("%s")' % partition
1845 else:
1846 if OPTIONS.source_info_dict is None:
1847 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1848 else:
1849 _, device_path = GetTypeAndDevice("/" + partition,
1850 OPTIONS.source_info_dict)
1851 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001852
Tao Baod8d14be2016-02-04 14:26:02 -08001853 @property
1854 def required_cache(self):
1855 return self._required_cache
1856
Tao Bao76def242017-11-21 09:25:31 -08001857 def WriteScript(self, script, output_zip, progress=None,
1858 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001859 if not self.src:
1860 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001861 script.Print("Patching %s image unconditionally..." % (self.partition,))
1862 else:
1863 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001864
Dan Albert8b72aef2015-03-23 19:13:21 -07001865 if progress:
1866 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001867 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001868
1869 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001870 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001871
Tao Bao9bc6bb22015-11-09 16:58:28 -08001872 def WriteStrictVerifyScript(self, script):
1873 """Verify all the blocks in the care_map, including clobbered blocks.
1874
1875 This differs from the WriteVerifyScript() function: a) it prints different
1876 error messages; b) it doesn't allow half-way updated images to pass the
1877 verification."""
1878
1879 partition = self.partition
1880 script.Print("Verifying %s..." % (partition,))
1881 ranges = self.tgt.care_map
1882 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001883 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001884 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1885 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001886 self.device, ranges_str,
1887 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001888 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001889 script.AppendExtra("")
1890
Tao Baod522bdc2016-04-12 15:53:16 -07001891 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001892 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001893
1894 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001895 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001896 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001897
1898 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001899 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001900 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001901 ranges = self.touched_src_ranges
1902 expected_sha1 = self.touched_src_sha1
1903 else:
1904 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1905 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001906
1907 # No blocks to be checked, skipping.
1908 if not ranges:
1909 return
1910
Tao Bao5ece99d2015-05-12 11:42:31 -07001911 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001912 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001913 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001914 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1915 '"%s.patch.dat")) then' % (
1916 self.device, ranges_str, expected_sha1,
1917 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001918 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001919 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001920
Tianjie Xufc3422a2015-12-15 11:53:59 -08001921 if self.version >= 4:
1922
1923 # Bug: 21124327
1924 # When generating incrementals for the system and vendor partitions in
1925 # version 4 or newer, explicitly check the first block (which contains
1926 # the superblock) of the partition to see if it's what we expect. If
1927 # this check fails, give an explicit log message about the partition
1928 # having been remounted R/W (the most likely explanation).
1929 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08001930 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08001931
1932 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001933 if partition == "system":
1934 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1935 else:
1936 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001937 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08001938 'ifelse (block_image_recover({device}, "{ranges}") && '
1939 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001940 'package_extract_file("{partition}.transfer.list"), '
1941 '"{partition}.new.dat", "{partition}.patch.dat"), '
1942 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001943 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001944 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001945 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001946
Tao Baodd2a5892015-03-12 12:32:37 -07001947 # Abort the OTA update. Note that the incremental OTA cannot be applied
1948 # even if it may match the checksum of the target partition.
1949 # a) If version < 3, operations like move and erase will make changes
1950 # unconditionally and damage the partition.
1951 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001952 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001953 if partition == "system":
1954 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1955 else:
1956 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1957 script.AppendExtra((
1958 'abort("E%d: %s partition has unexpected contents");\n'
1959 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001960
Yifan Hong10c530d2018-12-27 17:34:18 -08001961 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07001962 partition = self.partition
1963 script.Print('Verifying the updated %s image...' % (partition,))
1964 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1965 ranges = self.tgt.care_map
1966 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001967 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001968 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001969 self.device, ranges_str,
1970 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001971
1972 # Bug: 20881595
1973 # Verify that extended blocks are really zeroed out.
1974 if self.tgt.extended:
1975 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001976 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001977 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001978 self.device, ranges_str,
1979 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001980 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001981 if partition == "system":
1982 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1983 else:
1984 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001985 script.AppendExtra(
1986 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001987 ' abort("E%d: %s partition has unexpected non-zero contents after '
1988 'OTA update");\n'
1989 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001990 else:
1991 script.Print('Verified the updated %s image.' % (partition,))
1992
Tianjie Xu209db462016-05-24 17:34:52 -07001993 if partition == "system":
1994 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1995 else:
1996 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1997
Tao Bao5fcaaef2015-06-01 13:40:49 -07001998 script.AppendExtra(
1999 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002000 ' abort("E%d: %s partition has unexpected contents after OTA '
2001 'update");\n'
2002 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002003
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002004 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002005 ZipWrite(output_zip,
2006 '{}.transfer.list'.format(self.path),
2007 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002008
Tao Bao76def242017-11-21 09:25:31 -08002009 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2010 # its size. Quailty 9 almost triples the compression time but doesn't
2011 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002012 # zip | brotli(quality 6) | brotli(quality 9)
2013 # compressed_size: 942M | 869M (~8% reduced) | 854M
2014 # compression_time: 75s | 265s | 719s
2015 # decompression_time: 15s | 25s | 25s
2016
2017 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002018 brotli_cmd = ['brotli', '--quality=6',
2019 '--output={}.new.dat.br'.format(self.path),
2020 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002021 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002022 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002023
2024 new_data_name = '{}.new.dat.br'.format(self.partition)
2025 ZipWrite(output_zip,
2026 '{}.new.dat.br'.format(self.path),
2027 new_data_name,
2028 compress_type=zipfile.ZIP_STORED)
2029 else:
2030 new_data_name = '{}.new.dat'.format(self.partition)
2031 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2032
Dan Albert8e0178d2015-01-27 15:53:15 -08002033 ZipWrite(output_zip,
2034 '{}.patch.dat'.format(self.path),
2035 '{}.patch.dat'.format(self.partition),
2036 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002037
Tianjie Xu209db462016-05-24 17:34:52 -07002038 if self.partition == "system":
2039 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2040 else:
2041 code = ErrorCode.VENDOR_UPDATE_FAILURE
2042
Yifan Hong10c530d2018-12-27 17:34:18 -08002043 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002044 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002045 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002046 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002047 device=self.device, partition=self.partition,
2048 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002049 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002050
Dan Albert8b72aef2015-03-23 19:13:21 -07002051 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002052 data = source.ReadRangeSet(ranges)
2053 ctx = sha1()
2054
2055 for p in data:
2056 ctx.update(p)
2057
2058 return ctx.hexdigest()
2059
Tao Baoe9b61912015-07-09 17:37:49 -07002060 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2061 """Return the hash value for all zero blocks."""
2062 zero_block = '\x00' * 4096
2063 ctx = sha1()
2064 for _ in range(num_blocks):
2065 ctx.update(zero_block)
2066
2067 return ctx.hexdigest()
2068
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002069
2070DataImage = blockimgdiff.DataImage
2071
Tao Bao76def242017-11-21 09:25:31 -08002072
Doug Zongker96a57e72010-09-26 14:57:41 -07002073# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002074PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002075 "ext4": "EMMC",
2076 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002077 "f2fs": "EMMC",
2078 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002079}
Doug Zongker96a57e72010-09-26 14:57:41 -07002080
Tao Bao76def242017-11-21 09:25:31 -08002081
Doug Zongker96a57e72010-09-26 14:57:41 -07002082def GetTypeAndDevice(mount_point, info):
2083 fstab = info["fstab"]
2084 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002085 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2086 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002087 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002088 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002089
2090
2091def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002092 """Parses and converts a PEM-encoded certificate into DER-encoded.
2093
2094 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2095
2096 Returns:
2097 The decoded certificate string.
2098 """
2099 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002100 save = False
2101 for line in data.split("\n"):
2102 if "--END CERTIFICATE--" in line:
2103 break
2104 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002105 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002106 if "--BEGIN CERTIFICATE--" in line:
2107 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002108 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002109 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002110
Tao Bao04e1f012018-02-04 12:13:35 -08002111
2112def ExtractPublicKey(cert):
2113 """Extracts the public key (PEM-encoded) from the given certificate file.
2114
2115 Args:
2116 cert: The certificate filename.
2117
2118 Returns:
2119 The public key string.
2120
2121 Raises:
2122 AssertionError: On non-zero return from 'openssl'.
2123 """
2124 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2125 # While openssl 1.1 writes the key into the given filename followed by '-out',
2126 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2127 # stdout instead.
2128 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2129 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2130 pubkey, stderrdata = proc.communicate()
2131 assert proc.returncode == 0, \
2132 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2133 return pubkey
2134
2135
Tao Bao2cc0ca12019-03-15 10:44:43 -07002136def ExtractAvbPublicKey(key):
2137 """Extracts the AVB public key from the given public or private key.
2138
2139 Args:
2140 key: The input key file, which should be PEM-encoded public or private key.
2141
2142 Returns:
2143 The path to the extracted AVB public key file.
2144 """
2145 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2146 RunAndCheckOutput(
2147 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2148 return output
2149
2150
Doug Zongker412c02f2014-02-13 10:58:24 -08002151def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2152 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002153 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002154
Tao Bao6d5d6232018-03-09 17:04:42 -08002155 Most of the space in the boot and recovery images is just the kernel, which is
2156 identical for the two, so the resulting patch should be efficient. Add it to
2157 the output zip, along with a shell script that is run from init.rc on first
2158 boot to actually do the patching and install the new recovery image.
2159
2160 Args:
2161 input_dir: The top-level input directory of the target-files.zip.
2162 output_sink: The callback function that writes the result.
2163 recovery_img: File object for the recovery image.
2164 boot_img: File objects for the boot image.
2165 info_dict: A dict returned by common.LoadInfoDict() on the input
2166 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002167 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002168 if info_dict is None:
2169 info_dict = OPTIONS.info_dict
2170
Tao Bao6d5d6232018-03-09 17:04:42 -08002171 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002172
Tao Baof2cffbd2015-07-22 12:33:18 -07002173 if full_recovery_image:
2174 output_sink("etc/recovery.img", recovery_img.data)
2175
2176 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002177 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002178 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002179 # With system-root-image, boot and recovery images will have mismatching
2180 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2181 # to handle such a case.
2182 if system_root_image:
2183 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002184 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002185 assert not os.path.exists(path)
2186 else:
2187 diff_program = ["imgdiff"]
2188 if os.path.exists(path):
2189 diff_program.append("-b")
2190 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002191 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002192 else:
2193 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002194
2195 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2196 _, _, patch = d.ComputePatch()
2197 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002198
Dan Albertebb19aa2015-03-27 19:11:53 -07002199 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002200 # The following GetTypeAndDevice()s need to use the path in the target
2201 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002202 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2203 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2204 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002205 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002206
Tao Baof2cffbd2015-07-22 12:33:18 -07002207 if full_recovery_image:
2208 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002209if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2210 applypatch \\
2211 --flash /system/etc/recovery.img \\
2212 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2213 log -t recovery "Installing new recovery image: succeeded" || \\
2214 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002215else
2216 log -t recovery "Recovery image already installed"
2217fi
2218""" % {'type': recovery_type,
2219 'device': recovery_device,
2220 'sha1': recovery_img.sha1,
2221 'size': recovery_img.size}
2222 else:
2223 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002224if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2225 applypatch %(bonus_args)s \\
2226 --patch /system/recovery-from-boot.p \\
2227 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2228 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2229 log -t recovery "Installing new recovery image: succeeded" || \\
2230 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002231else
2232 log -t recovery "Recovery image already installed"
2233fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002234""" % {'boot_size': boot_img.size,
2235 'boot_sha1': boot_img.sha1,
2236 'recovery_size': recovery_img.size,
2237 'recovery_sha1': recovery_img.sha1,
2238 'boot_type': boot_type,
2239 'boot_device': boot_device,
2240 'recovery_type': recovery_type,
2241 'recovery_device': recovery_device,
2242 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002243
2244 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002245 # in the L release.
2246 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002247
Tao Bao32fcdab2018-10-12 10:30:39 -07002248 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002249
2250 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002251
2252
2253class DynamicPartitionUpdate(object):
2254 def __init__(self, src_group=None, tgt_group=None, progress=None,
2255 block_difference=None):
2256 self.src_group = src_group
2257 self.tgt_group = tgt_group
2258 self.progress = progress
2259 self.block_difference = block_difference
2260
2261 @property
2262 def src_size(self):
2263 if not self.block_difference:
2264 return 0
2265 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2266
2267 @property
2268 def tgt_size(self):
2269 if not self.block_difference:
2270 return 0
2271 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2272
2273 @staticmethod
2274 def _GetSparseImageSize(img):
2275 if not img:
2276 return 0
2277 return img.blocksize * img.total_blocks
2278
2279
2280class DynamicGroupUpdate(object):
2281 def __init__(self, src_size=None, tgt_size=None):
2282 # None: group does not exist. 0: no size limits.
2283 self.src_size = src_size
2284 self.tgt_size = tgt_size
2285
2286
2287class DynamicPartitionsDifference(object):
2288 def __init__(self, info_dict, block_diffs, progress_dict=None,
2289 source_info_dict=None):
2290 if progress_dict is None:
2291 progress_dict = dict()
2292
2293 self._remove_all_before_apply = False
2294 if source_info_dict is None:
2295 self._remove_all_before_apply = True
2296 source_info_dict = dict()
2297
2298 block_diff_dict = {e.partition:e for e in block_diffs}
2299 assert len(block_diff_dict) == len(block_diffs), \
2300 "Duplicated BlockDifference object for {}".format(
2301 [partition for partition, count in
2302 collections.Counter(e.partition for e in block_diffs).items()
2303 if count > 1])
2304
Yifan Hong79997e52019-01-23 16:56:19 -08002305 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002306
2307 for p, block_diff in block_diff_dict.items():
2308 self._partition_updates[p] = DynamicPartitionUpdate()
2309 self._partition_updates[p].block_difference = block_diff
2310
2311 for p, progress in progress_dict.items():
2312 if p in self._partition_updates:
2313 self._partition_updates[p].progress = progress
2314
2315 tgt_groups = shlex.split(info_dict.get(
2316 "super_partition_groups", "").strip())
2317 src_groups = shlex.split(source_info_dict.get(
2318 "super_partition_groups", "").strip())
2319
2320 for g in tgt_groups:
2321 for p in shlex.split(info_dict.get(
2322 "super_%s_partition_list" % g, "").strip()):
2323 assert p in self._partition_updates, \
2324 "{} is in target super_{}_partition_list but no BlockDifference " \
2325 "object is provided.".format(p, g)
2326 self._partition_updates[p].tgt_group = g
2327
2328 for g in src_groups:
2329 for p in shlex.split(source_info_dict.get(
2330 "super_%s_partition_list" % g, "").strip()):
2331 assert p in self._partition_updates, \
2332 "{} is in source super_{}_partition_list but no BlockDifference " \
2333 "object is provided.".format(p, g)
2334 self._partition_updates[p].src_group = g
2335
Yifan Hong45433e42019-01-18 13:55:25 -08002336 target_dynamic_partitions = set(shlex.split(info_dict.get(
2337 "dynamic_partition_list", "").strip()))
2338 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2339 if u.tgt_size)
2340 assert block_diffs_with_target == target_dynamic_partitions, \
2341 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2342 list(target_dynamic_partitions), list(block_diffs_with_target))
2343
2344 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2345 "dynamic_partition_list", "").strip()))
2346 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2347 if u.src_size)
2348 assert block_diffs_with_source == source_dynamic_partitions, \
2349 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2350 list(source_dynamic_partitions), list(block_diffs_with_source))
2351
Yifan Hong10c530d2018-12-27 17:34:18 -08002352 if self._partition_updates:
2353 logger.info("Updating dynamic partitions %s",
2354 self._partition_updates.keys())
2355
Yifan Hong79997e52019-01-23 16:56:19 -08002356 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002357
2358 for g in tgt_groups:
2359 self._group_updates[g] = DynamicGroupUpdate()
2360 self._group_updates[g].tgt_size = int(info_dict.get(
2361 "super_%s_group_size" % g, "0").strip())
2362
2363 for g in src_groups:
2364 if g not in self._group_updates:
2365 self._group_updates[g] = DynamicGroupUpdate()
2366 self._group_updates[g].src_size = int(source_info_dict.get(
2367 "super_%s_group_size" % g, "0").strip())
2368
2369 self._Compute()
2370
2371 def WriteScript(self, script, output_zip, write_verify_script=False):
2372 script.Comment('--- Start patching dynamic partitions ---')
2373 for p, u in self._partition_updates.items():
2374 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2375 script.Comment('Patch partition %s' % p)
2376 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2377 write_verify_script=False)
2378
2379 op_list_path = MakeTempFile()
2380 with open(op_list_path, 'w') as f:
2381 for line in self._op_list:
2382 f.write('{}\n'.format(line))
2383
2384 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2385
2386 script.Comment('Update dynamic partition metadata')
2387 script.AppendExtra('assert(update_dynamic_partitions('
2388 'package_extract_file("dynamic_partitions_op_list")));')
2389
2390 if write_verify_script:
2391 for p, u in self._partition_updates.items():
2392 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2393 u.block_difference.WritePostInstallVerifyScript(script)
2394 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2395
2396 for p, u in self._partition_updates.items():
2397 if u.tgt_size and u.src_size <= u.tgt_size:
2398 script.Comment('Patch partition %s' % p)
2399 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2400 write_verify_script=write_verify_script)
2401 if write_verify_script:
2402 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2403
2404 script.Comment('--- End patching dynamic partitions ---')
2405
2406 def _Compute(self):
2407 self._op_list = list()
2408
2409 def append(line):
2410 self._op_list.append(line)
2411
2412 def comment(line):
2413 self._op_list.append("# %s" % line)
2414
2415 if self._remove_all_before_apply:
2416 comment('Remove all existing dynamic partitions and groups before '
2417 'applying full OTA')
2418 append('remove_all_groups')
2419
2420 for p, u in self._partition_updates.items():
2421 if u.src_group and not u.tgt_group:
2422 append('remove %s' % p)
2423
2424 for p, u in self._partition_updates.items():
2425 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2426 comment('Move partition %s from %s to default' % (p, u.src_group))
2427 append('move %s default' % p)
2428
2429 for p, u in self._partition_updates.items():
2430 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2431 comment('Shrink partition %s from %d to %d' %
2432 (p, u.src_size, u.tgt_size))
2433 append('resize %s %s' % (p, u.tgt_size))
2434
2435 for g, u in self._group_updates.items():
2436 if u.src_size is not None and u.tgt_size is None:
2437 append('remove_group %s' % g)
2438 if (u.src_size is not None and u.tgt_size is not None and
2439 u.src_size > u.tgt_size):
2440 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2441 append('resize_group %s %d' % (g, u.tgt_size))
2442
2443 for g, u in self._group_updates.items():
2444 if u.src_size is None and u.tgt_size is not None:
2445 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2446 append('add_group %s %d' % (g, u.tgt_size))
2447 if (u.src_size is not None and u.tgt_size is not None and
2448 u.src_size < u.tgt_size):
2449 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2450 append('resize_group %s %d' % (g, u.tgt_size))
2451
2452 for p, u in self._partition_updates.items():
2453 if u.tgt_group and not u.src_group:
2454 comment('Add partition %s to group %s' % (p, u.tgt_group))
2455 append('add %s %s' % (p, u.tgt_group))
2456
2457 for p, u in self._partition_updates.items():
2458 if u.tgt_size and u.src_size < u.tgt_size:
2459 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2460 append('resize %s %d' % (p, u.tgt_size))
2461
2462 for p, u in self._partition_updates.items():
2463 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2464 comment('Move partition %s from default to %s' %
2465 (p, u.tgt_group))
2466 append('move %s %s' % (p, u.tgt_group))