blob: 3e2a113e06bde0ed5ab5c0272f6822085ca1f9bb [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 Baobf70c312017-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 Baobf70c312017-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 Baobf70c312017-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 Baobf70c312017-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
Yifan Hong8a66a712019-04-04 15:37:57 -0700827def GetUserImage(which, tmpdir, input_zip,
828 info_dict=None,
829 allow_shared_blocks=None,
830 hashtree_info_generator=None,
831 reset_file_map=False):
832 """Returns an Image object suitable for passing to BlockImageDiff.
833
834 This function loads the specified image from the given path. If the specified
835 image is sparse, it also performs additional processing for OTA purpose. For
836 example, it always adds block 0 to clobbered blocks list. It also detects
837 files that cannot be reconstructed from the block list, for whom we should
838 avoid applying imgdiff.
839
840 Args:
841 which: The partition name.
842 tmpdir: The directory that contains the prebuilt image and block map file.
843 input_zip: The target-files ZIP archive.
844 info_dict: The dict to be looked up for relevant info.
845 allow_shared_blocks: If image is sparse, whether having shared blocks is
846 allowed. If none, it is looked up from info_dict.
847 hashtree_info_generator: If present and image is sparse, generates the
848 hashtree_info for this sparse image.
849 reset_file_map: If true and image is sparse, reset file map before returning
850 the image.
851 Returns:
852 A Image object. If it is a sparse image and reset_file_map is False, the
853 image will have file_map info loaded.
854 """
855 if info_dict == None:
856 info_dict = LoadInfoDict(input_zip)
857
858 is_sparse = info_dict.get("extfs_sparse_flag")
859
860 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
861 # shared blocks (i.e. some blocks will show up in multiple files' block
862 # list). We can only allocate such shared blocks to the first "owner", and
863 # disable imgdiff for all later occurrences.
864 if allow_shared_blocks is None:
865 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
866
867 if is_sparse:
868 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
869 hashtree_info_generator)
870 if reset_file_map:
871 img.ResetFileMap()
872 return img
873 else:
874 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
875
876
877def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
878 """Returns a Image object suitable for passing to BlockImageDiff.
879
880 This function loads the specified non-sparse image from the given path.
881
882 Args:
883 which: The partition name.
884 tmpdir: The directory that contains the prebuilt image and block map file.
885 Returns:
886 A Image object.
887 """
888 path = os.path.join(tmpdir, "IMAGES", which + ".img")
889 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
890
891 # The image and map files must have been created prior to calling
892 # ota_from_target_files.py (since LMP).
893 assert os.path.exists(path) and os.path.exists(mappath)
894
895 return blockimgdiff.FileImage(path, hashtree_info_generator=
896 hashtree_info_generator)
897
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700898def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
899 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800900 """Returns a SparseImage object suitable for passing to BlockImageDiff.
901
902 This function loads the specified sparse image from the given path, and
903 performs additional processing for OTA purpose. For example, it always adds
904 block 0 to clobbered blocks list. It also detects files that cannot be
905 reconstructed from the block list, for whom we should avoid applying imgdiff.
906
907 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700908 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800909 tmpdir: The directory that contains the prebuilt image and block map file.
910 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800911 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700912 hashtree_info_generator: If present, generates the hashtree_info for this
913 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800914 Returns:
915 A SparseImage object, with file_map info loaded.
916 """
Tao Baoc765cca2018-01-31 17:32:40 -0800917 path = os.path.join(tmpdir, "IMAGES", which + ".img")
918 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
919
920 # The image and map files must have been created prior to calling
921 # ota_from_target_files.py (since LMP).
922 assert os.path.exists(path) and os.path.exists(mappath)
923
924 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
925 # it to clobbered_blocks so that it will be written to the target
926 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
927 clobbered_blocks = "0"
928
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700929 image = sparse_img.SparseImage(
930 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
931 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800932
933 # block.map may contain less blocks, because mke2fs may skip allocating blocks
934 # if they contain all zeros. We can't reconstruct such a file from its block
935 # list. Tag such entries accordingly. (Bug: 65213616)
936 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800937 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700938 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800939 continue
940
Tom Cherryd14b8952018-08-09 14:26:00 -0700941 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
942 # filename listed in system.map may contain an additional leading slash
943 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
944 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700945 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
946
Tom Cherryd14b8952018-08-09 14:26:00 -0700947 # Special handling another case, where files not under /system
948 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700949 if which == 'system' and not arcname.startswith('SYSTEM'):
950 arcname = 'ROOT/' + arcname
951
952 assert arcname in input_zip.namelist(), \
953 "Failed to find the ZIP entry for {}".format(entry)
954
Tao Baoc765cca2018-01-31 17:32:40 -0800955 info = input_zip.getinfo(arcname)
956 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800957
958 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800959 # image, check the original block list to determine its completeness. Note
960 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800961 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800962 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800963
Tao Baoc765cca2018-01-31 17:32:40 -0800964 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
965 ranges.extra['incomplete'] = True
966
967 return image
968
969
Doug Zongkereef39442009-04-02 12:14:19 -0700970def GetKeyPasswords(keylist):
971 """Given a list of keys, prompt the user to enter passwords for
972 those which require them. Return a {key: password} dict. password
973 will be None if the key has no password."""
974
Doug Zongker8ce7c252009-05-22 13:34:54 -0700975 no_passwords = []
976 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700977 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700978 devnull = open("/dev/null", "w+b")
979 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800980 # We don't need a password for things that aren't really keys.
981 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700982 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700983 continue
984
T.R. Fullhart37e10522013-03-18 10:31:26 -0700985 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700986 "-inform", "DER", "-nocrypt"],
987 stdin=devnull.fileno(),
988 stdout=devnull.fileno(),
989 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700990 p.communicate()
991 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700992 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700993 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700994 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700995 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
996 "-inform", "DER", "-passin", "pass:"],
997 stdin=devnull.fileno(),
998 stdout=devnull.fileno(),
999 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001000 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001001 if p.returncode == 0:
1002 # Encrypted key with empty string as password.
1003 key_passwords[k] = ''
1004 elif stderr.startswith('Error decrypting key'):
1005 # Definitely encrypted key.
1006 # It would have said "Error reading key" if it didn't parse correctly.
1007 need_passwords.append(k)
1008 else:
1009 # Potentially, a type of key that openssl doesn't understand.
1010 # We'll let the routines in signapk.jar handle it.
1011 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001012 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001013
T.R. Fullhart37e10522013-03-18 10:31:26 -07001014 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001015 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001016 return key_passwords
1017
1018
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001019def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001020 """Gets the minSdkVersion declared in the APK.
1021
1022 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1023 This can be both a decimal number (API Level) or a codename.
1024
1025 Args:
1026 apk_name: The APK filename.
1027
1028 Returns:
1029 The parsed SDK version string.
1030
1031 Raises:
1032 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001033 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001034 proc = Run(
1035 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1036 stderr=subprocess.PIPE)
1037 stdoutdata, stderrdata = proc.communicate()
1038 if proc.returncode != 0:
1039 raise ExternalError(
1040 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1041 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001042
Tao Baof47bf0f2018-03-21 23:28:51 -07001043 for line in stdoutdata.split("\n"):
1044 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001045 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1046 if m:
1047 return m.group(1)
1048 raise ExternalError("No minSdkVersion returned by aapt")
1049
1050
1051def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001052 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001053
Tao Baof47bf0f2018-03-21 23:28:51 -07001054 If minSdkVersion is set to a codename, it is translated to a number using the
1055 provided map.
1056
1057 Args:
1058 apk_name: The APK filename.
1059
1060 Returns:
1061 The parsed SDK version number.
1062
1063 Raises:
1064 ExternalError: On failing to get the min SDK version number.
1065 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001066 version = GetMinSdkVersion(apk_name)
1067 try:
1068 return int(version)
1069 except ValueError:
1070 # Not a decimal number. Codename?
1071 if version in codename_to_api_level_map:
1072 return codename_to_api_level_map[version]
1073 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001074 raise ExternalError(
1075 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1076 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001077
1078
1079def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001080 codename_to_api_level_map=None, whole_file=False,
1081 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001082 """Sign the input_name zip/jar/apk, producing output_name. Use the
1083 given key and password (the latter may be None if the key does not
1084 have a password.
1085
Doug Zongker951495f2009-08-14 12:44:19 -07001086 If whole_file is true, use the "-w" option to SignApk to embed a
1087 signature that covers the whole file in the archive comment of the
1088 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001089
1090 min_api_level is the API Level (int) of the oldest platform this file may end
1091 up on. If not specified for an APK, the API Level is obtained by interpreting
1092 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1093
1094 codename_to_api_level_map is needed to translate the codename which may be
1095 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001096
1097 Caller may optionally specify extra args to be passed to SignApk, which
1098 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001099 """
Tao Bao76def242017-11-21 09:25:31 -08001100 if codename_to_api_level_map is None:
1101 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001102 if extra_signapk_args is None:
1103 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001104
Alex Klyubin9667b182015-12-10 13:38:50 -08001105 java_library_path = os.path.join(
1106 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1107
Tao Baoe95540e2016-11-08 12:08:53 -08001108 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1109 ["-Djava.library.path=" + java_library_path,
1110 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001111 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001112 if whole_file:
1113 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001114
1115 min_sdk_version = min_api_level
1116 if min_sdk_version is None:
1117 if not whole_file:
1118 min_sdk_version = GetMinSdkVersionInt(
1119 input_name, codename_to_api_level_map)
1120 if min_sdk_version is not None:
1121 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1122
T.R. Fullhart37e10522013-03-18 10:31:26 -07001123 cmd.extend([key + OPTIONS.public_key_suffix,
1124 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001125 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001126
Tao Bao73dd4f42018-10-04 16:25:33 -07001127 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001128 if password is not None:
1129 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001130 stdoutdata, _ = proc.communicate(password)
1131 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001132 raise ExternalError(
1133 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001134 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001135
Doug Zongkereef39442009-04-02 12:14:19 -07001136
Doug Zongker37974732010-09-16 17:44:38 -07001137def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001138 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001139
Tao Bao9dd909e2017-11-14 11:27:32 -08001140 For non-AVB images, raise exception if the data is too big. Print a warning
1141 if the data is nearing the maximum size.
1142
1143 For AVB images, the actual image size should be identical to the limit.
1144
1145 Args:
1146 data: A string that contains all the data for the partition.
1147 target: The partition name. The ".img" suffix is optional.
1148 info_dict: The dict to be looked up for relevant info.
1149 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001150 if target.endswith(".img"):
1151 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001152 mount_point = "/" + target
1153
Ying Wangf8824af2014-06-03 14:07:27 -07001154 fs_type = None
1155 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001156 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001157 if mount_point == "/userdata":
1158 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001159 p = info_dict["fstab"][mount_point]
1160 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001161 device = p.device
1162 if "/" in device:
1163 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001164 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001165 if not fs_type or not limit:
1166 return
Doug Zongkereef39442009-04-02 12:14:19 -07001167
Andrew Boie0f9aec82012-02-14 09:32:52 -08001168 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001169 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1170 # path.
1171 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1172 if size != limit:
1173 raise ExternalError(
1174 "Mismatching image size for %s: expected %d actual %d" % (
1175 target, limit, size))
1176 else:
1177 pct = float(size) * 100.0 / limit
1178 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1179 if pct >= 99.0:
1180 raise ExternalError(msg)
1181 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001182 logger.warning("\n WARNING: %s\n", msg)
1183 else:
1184 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001185
1186
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001187def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001188 """Parses the APK certs info from a given target-files zip.
1189
1190 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1191 tuple with the following elements: (1) a dictionary that maps packages to
1192 certs (based on the "certificate" and "private_key" attributes in the file;
1193 (2) a string representing the extension of compressed APKs in the target files
1194 (e.g ".gz", ".bro").
1195
1196 Args:
1197 tf_zip: The input target_files ZipFile (already open).
1198
1199 Returns:
1200 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1201 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1202 no compressed APKs.
1203 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001204 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001205 compressed_extension = None
1206
Tao Bao0f990332017-09-08 19:02:54 -07001207 # META/apkcerts.txt contains the info for _all_ the packages known at build
1208 # time. Filter out the ones that are not installed.
1209 installed_files = set()
1210 for name in tf_zip.namelist():
1211 basename = os.path.basename(name)
1212 if basename:
1213 installed_files.add(basename)
1214
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001215 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1216 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001217 if not line:
1218 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001219 m = re.match(
1220 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1221 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1222 line)
1223 if not m:
1224 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001225
Tao Bao818ddf52018-01-05 11:17:34 -08001226 matches = m.groupdict()
1227 cert = matches["CERT"]
1228 privkey = matches["PRIVKEY"]
1229 name = matches["NAME"]
1230 this_compressed_extension = matches["COMPRESSED"]
1231
1232 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1233 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1234 if cert in SPECIAL_CERT_STRINGS and not privkey:
1235 certmap[name] = cert
1236 elif (cert.endswith(OPTIONS.public_key_suffix) and
1237 privkey.endswith(OPTIONS.private_key_suffix) and
1238 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1239 certmap[name] = cert[:-public_key_suffix_len]
1240 else:
1241 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1242
1243 if not this_compressed_extension:
1244 continue
1245
1246 # Only count the installed files.
1247 filename = name + '.' + this_compressed_extension
1248 if filename not in installed_files:
1249 continue
1250
1251 # Make sure that all the values in the compression map have the same
1252 # extension. We don't support multiple compression methods in the same
1253 # system image.
1254 if compressed_extension:
1255 if this_compressed_extension != compressed_extension:
1256 raise ValueError(
1257 "Multiple compressed extensions: {} vs {}".format(
1258 compressed_extension, this_compressed_extension))
1259 else:
1260 compressed_extension = this_compressed_extension
1261
1262 return (certmap,
1263 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001264
1265
Doug Zongkereef39442009-04-02 12:14:19 -07001266COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001267Global options
1268
1269 -p (--path) <dir>
1270 Prepend <dir>/bin to the list of places to search for binaries run by this
1271 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001272
Doug Zongker05d3dea2009-06-22 11:32:31 -07001273 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001274 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001275
Tao Bao30df8b42018-04-23 15:32:53 -07001276 -x (--extra) <key=value>
1277 Add a key/value pair to the 'extras' dict, which device-specific extension
1278 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001279
Doug Zongkereef39442009-04-02 12:14:19 -07001280 -v (--verbose)
1281 Show command lines being executed.
1282
1283 -h (--help)
1284 Display this usage message and exit.
1285"""
1286
1287def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001288 print(docstring.rstrip("\n"))
1289 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001290
1291
1292def ParseOptions(argv,
1293 docstring,
1294 extra_opts="", extra_long_opts=(),
1295 extra_option_handler=None):
1296 """Parse the options in argv and return any arguments that aren't
1297 flags. docstring is the calling module's docstring, to be displayed
1298 for errors and -h. extra_opts and extra_long_opts are for flags
1299 defined by the caller, which are processed by passing them to
1300 extra_option_handler."""
1301
1302 try:
1303 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001304 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001305 ["help", "verbose", "path=", "signapk_path=",
1306 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001307 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001308 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1309 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001310 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001311 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001312 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001313 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001314 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001315 sys.exit(2)
1316
Doug Zongkereef39442009-04-02 12:14:19 -07001317 for o, a in opts:
1318 if o in ("-h", "--help"):
1319 Usage(docstring)
1320 sys.exit()
1321 elif o in ("-v", "--verbose"):
1322 OPTIONS.verbose = True
1323 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001324 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001325 elif o in ("--signapk_path",):
1326 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001327 elif o in ("--signapk_shared_library_path",):
1328 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001329 elif o in ("--extra_signapk_args",):
1330 OPTIONS.extra_signapk_args = shlex.split(a)
1331 elif o in ("--java_path",):
1332 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001333 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001334 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001335 elif o in ("--public_key_suffix",):
1336 OPTIONS.public_key_suffix = a
1337 elif o in ("--private_key_suffix",):
1338 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001339 elif o in ("--boot_signer_path",):
1340 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001341 elif o in ("--boot_signer_args",):
1342 OPTIONS.boot_signer_args = shlex.split(a)
1343 elif o in ("--verity_signer_path",):
1344 OPTIONS.verity_signer_path = a
1345 elif o in ("--verity_signer_args",):
1346 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001347 elif o in ("-s", "--device_specific"):
1348 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001349 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001350 key, value = a.split("=", 1)
1351 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001352 else:
1353 if extra_option_handler is None or not extra_option_handler(o, a):
1354 assert False, "unknown option \"%s\"" % (o,)
1355
Doug Zongker85448772014-09-09 14:59:20 -07001356 if OPTIONS.search_path:
1357 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1358 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001359
1360 return args
1361
1362
Tao Bao4c851b12016-09-19 13:54:38 -07001363def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001364 """Make a temp file and add it to the list of things to be deleted
1365 when Cleanup() is called. Return the filename."""
1366 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1367 os.close(fd)
1368 OPTIONS.tempfiles.append(fn)
1369 return fn
1370
1371
Tao Bao1c830bf2017-12-25 10:43:47 -08001372def MakeTempDir(prefix='tmp', suffix=''):
1373 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1374
1375 Returns:
1376 The absolute pathname of the new directory.
1377 """
1378 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1379 OPTIONS.tempfiles.append(dir_name)
1380 return dir_name
1381
1382
Doug Zongkereef39442009-04-02 12:14:19 -07001383def Cleanup():
1384 for i in OPTIONS.tempfiles:
1385 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001386 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001387 else:
1388 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001389 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001390
1391
1392class PasswordManager(object):
1393 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001394 self.editor = os.getenv("EDITOR")
1395 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001396
1397 def GetPasswords(self, items):
1398 """Get passwords corresponding to each string in 'items',
1399 returning a dict. (The dict may have keys in addition to the
1400 values in 'items'.)
1401
1402 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1403 user edit that file to add more needed passwords. If no editor is
1404 available, or $ANDROID_PW_FILE isn't define, prompts the user
1405 interactively in the ordinary way.
1406 """
1407
1408 current = self.ReadFile()
1409
1410 first = True
1411 while True:
1412 missing = []
1413 for i in items:
1414 if i not in current or not current[i]:
1415 missing.append(i)
1416 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001417 if not missing:
1418 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001419
1420 for i in missing:
1421 current[i] = ""
1422
1423 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001424 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001425 answer = raw_input("try to edit again? [y]> ").strip()
1426 if answer and answer[0] not in 'yY':
1427 raise RuntimeError("key passwords unavailable")
1428 first = False
1429
1430 current = self.UpdateAndReadFile(current)
1431
Dan Albert8b72aef2015-03-23 19:13:21 -07001432 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001433 """Prompt the user to enter a value (password) for each key in
1434 'current' whose value is fales. Returns a new dict with all the
1435 values.
1436 """
1437 result = {}
1438 for k, v in sorted(current.iteritems()):
1439 if v:
1440 result[k] = v
1441 else:
1442 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001443 result[k] = getpass.getpass(
1444 "Enter password for %s key> " % k).strip()
1445 if result[k]:
1446 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001447 return result
1448
1449 def UpdateAndReadFile(self, current):
1450 if not self.editor or not self.pwfile:
1451 return self.PromptResult(current)
1452
1453 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001454 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001455 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1456 f.write("# (Additional spaces are harmless.)\n\n")
1457
1458 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001459 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1460 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001461 f.write("[[[ %s ]]] %s\n" % (v, k))
1462 if not v and first_line is None:
1463 # position cursor on first line with no password.
1464 first_line = i + 4
1465 f.close()
1466
Tao Bao986ee862018-10-04 15:46:16 -07001467 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001468
1469 return self.ReadFile()
1470
1471 def ReadFile(self):
1472 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001473 if self.pwfile is None:
1474 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001475 try:
1476 f = open(self.pwfile, "r")
1477 for line in f:
1478 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001479 if not line or line[0] == '#':
1480 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001481 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1482 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001483 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001484 else:
1485 result[m.group(2)] = m.group(1)
1486 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001487 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001488 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001489 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001490 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001491
1492
Dan Albert8e0178d2015-01-27 15:53:15 -08001493def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1494 compress_type=None):
1495 import datetime
1496
1497 # http://b/18015246
1498 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1499 # for files larger than 2GiB. We can work around this by adjusting their
1500 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1501 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1502 # it isn't clear to me exactly what circumstances cause this).
1503 # `zipfile.write()` must be used directly to work around this.
1504 #
1505 # This mess can be avoided if we port to python3.
1506 saved_zip64_limit = zipfile.ZIP64_LIMIT
1507 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1508
1509 if compress_type is None:
1510 compress_type = zip_file.compression
1511 if arcname is None:
1512 arcname = filename
1513
1514 saved_stat = os.stat(filename)
1515
1516 try:
1517 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1518 # file to be zipped and reset it when we're done.
1519 os.chmod(filename, perms)
1520
1521 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001522 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1523 # intentional. zip stores datetimes in local time without a time zone
1524 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1525 # in the zip archive.
1526 local_epoch = datetime.datetime.fromtimestamp(0)
1527 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001528 os.utime(filename, (timestamp, timestamp))
1529
1530 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1531 finally:
1532 os.chmod(filename, saved_stat.st_mode)
1533 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1534 zipfile.ZIP64_LIMIT = saved_zip64_limit
1535
1536
Tao Bao58c1b962015-05-20 09:32:18 -07001537def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001538 compress_type=None):
1539 """Wrap zipfile.writestr() function to work around the zip64 limit.
1540
1541 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1542 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1543 when calling crc32(bytes).
1544
1545 But it still works fine to write a shorter string into a large zip file.
1546 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1547 when we know the string won't be too long.
1548 """
1549
1550 saved_zip64_limit = zipfile.ZIP64_LIMIT
1551 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1552
1553 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1554 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001555 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001556 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001557 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001558 else:
Tao Baof3282b42015-04-01 11:21:55 -07001559 zinfo = zinfo_or_arcname
1560
1561 # If compress_type is given, it overrides the value in zinfo.
1562 if compress_type is not None:
1563 zinfo.compress_type = compress_type
1564
Tao Bao58c1b962015-05-20 09:32:18 -07001565 # If perms is given, it has a priority.
1566 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001567 # If perms doesn't set the file type, mark it as a regular file.
1568 if perms & 0o770000 == 0:
1569 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001570 zinfo.external_attr = perms << 16
1571
Tao Baof3282b42015-04-01 11:21:55 -07001572 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001573 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1574
Dan Albert8b72aef2015-03-23 19:13:21 -07001575 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001576 zipfile.ZIP64_LIMIT = saved_zip64_limit
1577
1578
Tao Bao89d7ab22017-12-14 17:05:33 -08001579def ZipDelete(zip_filename, entries):
1580 """Deletes entries from a ZIP file.
1581
1582 Since deleting entries from a ZIP file is not supported, it shells out to
1583 'zip -d'.
1584
1585 Args:
1586 zip_filename: The name of the ZIP file.
1587 entries: The name of the entry, or the list of names to be deleted.
1588
1589 Raises:
1590 AssertionError: In case of non-zero return from 'zip'.
1591 """
1592 if isinstance(entries, basestring):
1593 entries = [entries]
1594 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001595 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001596
1597
Tao Baof3282b42015-04-01 11:21:55 -07001598def ZipClose(zip_file):
1599 # http://b/18015246
1600 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1601 # central directory.
1602 saved_zip64_limit = zipfile.ZIP64_LIMIT
1603 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1604
1605 zip_file.close()
1606
1607 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001608
1609
1610class DeviceSpecificParams(object):
1611 module = None
1612 def __init__(self, **kwargs):
1613 """Keyword arguments to the constructor become attributes of this
1614 object, which is passed to all functions in the device-specific
1615 module."""
1616 for k, v in kwargs.iteritems():
1617 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001618 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001619
1620 if self.module is None:
1621 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001622 if not path:
1623 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001624 try:
1625 if os.path.isdir(path):
1626 info = imp.find_module("releasetools", [path])
1627 else:
1628 d, f = os.path.split(path)
1629 b, x = os.path.splitext(f)
1630 if x == ".py":
1631 f = b
1632 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001633 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001634 self.module = imp.load_module("device_specific", *info)
1635 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001636 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001637
1638 def _DoCall(self, function_name, *args, **kwargs):
1639 """Call the named function in the device-specific module, passing
1640 the given args and kwargs. The first argument to the call will be
1641 the DeviceSpecific object itself. If there is no module, or the
1642 module does not define the function, return the value of the
1643 'default' kwarg (which itself defaults to None)."""
1644 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001645 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001646 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1647
1648 def FullOTA_Assertions(self):
1649 """Called after emitting the block of assertions at the top of a
1650 full OTA package. Implementations can add whatever additional
1651 assertions they like."""
1652 return self._DoCall("FullOTA_Assertions")
1653
Doug Zongkere5ff5902012-01-17 10:55:37 -08001654 def FullOTA_InstallBegin(self):
1655 """Called at the start of full OTA installation."""
1656 return self._DoCall("FullOTA_InstallBegin")
1657
Yifan Hong10c530d2018-12-27 17:34:18 -08001658 def FullOTA_GetBlockDifferences(self):
1659 """Called during full OTA installation and verification.
1660 Implementation should return a list of BlockDifference objects describing
1661 the update on each additional partitions.
1662 """
1663 return self._DoCall("FullOTA_GetBlockDifferences")
1664
Doug Zongker05d3dea2009-06-22 11:32:31 -07001665 def FullOTA_InstallEnd(self):
1666 """Called at the end of full OTA installation; typically this is
1667 used to install the image for the device's baseband processor."""
1668 return self._DoCall("FullOTA_InstallEnd")
1669
1670 def IncrementalOTA_Assertions(self):
1671 """Called after emitting the block of assertions at the top of an
1672 incremental OTA package. Implementations can add whatever
1673 additional assertions they like."""
1674 return self._DoCall("IncrementalOTA_Assertions")
1675
Doug Zongkere5ff5902012-01-17 10:55:37 -08001676 def IncrementalOTA_VerifyBegin(self):
1677 """Called at the start of the verification phase of incremental
1678 OTA installation; additional checks can be placed here to abort
1679 the script before any changes are made."""
1680 return self._DoCall("IncrementalOTA_VerifyBegin")
1681
Doug Zongker05d3dea2009-06-22 11:32:31 -07001682 def IncrementalOTA_VerifyEnd(self):
1683 """Called at the end of the verification phase of incremental OTA
1684 installation; additional checks can be placed here to abort the
1685 script before any changes are made."""
1686 return self._DoCall("IncrementalOTA_VerifyEnd")
1687
Doug Zongkere5ff5902012-01-17 10:55:37 -08001688 def IncrementalOTA_InstallBegin(self):
1689 """Called at the start of incremental OTA installation (after
1690 verification is complete)."""
1691 return self._DoCall("IncrementalOTA_InstallBegin")
1692
Yifan Hong10c530d2018-12-27 17:34:18 -08001693 def IncrementalOTA_GetBlockDifferences(self):
1694 """Called during incremental OTA installation and verification.
1695 Implementation should return a list of BlockDifference objects describing
1696 the update on each additional partitions.
1697 """
1698 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1699
Doug Zongker05d3dea2009-06-22 11:32:31 -07001700 def IncrementalOTA_InstallEnd(self):
1701 """Called at the end of incremental OTA installation; typically
1702 this is used to install the image for the device's baseband
1703 processor."""
1704 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001705
Tao Bao9bc6bb22015-11-09 16:58:28 -08001706 def VerifyOTA_Assertions(self):
1707 return self._DoCall("VerifyOTA_Assertions")
1708
Tao Bao76def242017-11-21 09:25:31 -08001709
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001710class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001711 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001712 self.name = name
1713 self.data = data
1714 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001715 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001716 self.sha1 = sha1(data).hexdigest()
1717
1718 @classmethod
1719 def FromLocalFile(cls, name, diskname):
1720 f = open(diskname, "rb")
1721 data = f.read()
1722 f.close()
1723 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001724
1725 def WriteToTemp(self):
1726 t = tempfile.NamedTemporaryFile()
1727 t.write(self.data)
1728 t.flush()
1729 return t
1730
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001731 def WriteToDir(self, d):
1732 with open(os.path.join(d, self.name), "wb") as fp:
1733 fp.write(self.data)
1734
Geremy Condra36bd3652014-02-06 19:45:10 -08001735 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001736 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001737
Tao Bao76def242017-11-21 09:25:31 -08001738
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001739DIFF_PROGRAM_BY_EXT = {
1740 ".gz" : "imgdiff",
1741 ".zip" : ["imgdiff", "-z"],
1742 ".jar" : ["imgdiff", "-z"],
1743 ".apk" : ["imgdiff", "-z"],
1744 ".img" : "imgdiff",
1745 }
1746
Tao Bao76def242017-11-21 09:25:31 -08001747
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001748class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001749 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001750 self.tf = tf
1751 self.sf = sf
1752 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001753 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001754
1755 def ComputePatch(self):
1756 """Compute the patch (as a string of data) needed to turn sf into
1757 tf. Returns the same tuple as GetPatch()."""
1758
1759 tf = self.tf
1760 sf = self.sf
1761
Doug Zongker24cd2802012-08-14 16:36:15 -07001762 if self.diff_program:
1763 diff_program = self.diff_program
1764 else:
1765 ext = os.path.splitext(tf.name)[1]
1766 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001767
1768 ttemp = tf.WriteToTemp()
1769 stemp = sf.WriteToTemp()
1770
1771 ext = os.path.splitext(tf.name)[1]
1772
1773 try:
1774 ptemp = tempfile.NamedTemporaryFile()
1775 if isinstance(diff_program, list):
1776 cmd = copy.copy(diff_program)
1777 else:
1778 cmd = [diff_program]
1779 cmd.append(stemp.name)
1780 cmd.append(ttemp.name)
1781 cmd.append(ptemp.name)
1782 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001783 err = []
1784 def run():
1785 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001786 if e:
1787 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001788 th = threading.Thread(target=run)
1789 th.start()
1790 th.join(timeout=300) # 5 mins
1791 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001792 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001793 p.terminate()
1794 th.join(5)
1795 if th.is_alive():
1796 p.kill()
1797 th.join()
1798
Tianjie Xua2a9f992018-01-05 15:15:54 -08001799 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001800 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001801 self.patch = None
1802 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001803 diff = ptemp.read()
1804 finally:
1805 ptemp.close()
1806 stemp.close()
1807 ttemp.close()
1808
1809 self.patch = diff
1810 return self.tf, self.sf, self.patch
1811
1812
1813 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001814 """Returns a tuple of (target_file, source_file, patch_data).
1815
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001816 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001817 computing the patch failed.
1818 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001819 return self.tf, self.sf, self.patch
1820
1821
1822def ComputeDifferences(diffs):
1823 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001824 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001825
1826 # Do the largest files first, to try and reduce the long-pole effect.
1827 by_size = [(i.tf.size, i) for i in diffs]
1828 by_size.sort(reverse=True)
1829 by_size = [i[1] for i in by_size]
1830
1831 lock = threading.Lock()
1832 diff_iter = iter(by_size) # accessed under lock
1833
1834 def worker():
1835 try:
1836 lock.acquire()
1837 for d in diff_iter:
1838 lock.release()
1839 start = time.time()
1840 d.ComputePatch()
1841 dur = time.time() - start
1842 lock.acquire()
1843
1844 tf, sf, patch = d.GetPatch()
1845 if sf.name == tf.name:
1846 name = tf.name
1847 else:
1848 name = "%s (%s)" % (tf.name, sf.name)
1849 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001850 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001851 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001852 logger.info(
1853 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1854 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001855 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001856 except Exception:
1857 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001858 raise
1859
1860 # start worker threads; wait for them all to finish.
1861 threads = [threading.Thread(target=worker)
1862 for i in range(OPTIONS.worker_threads)]
1863 for th in threads:
1864 th.start()
1865 while threads:
1866 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001867
1868
Dan Albert8b72aef2015-03-23 19:13:21 -07001869class BlockDifference(object):
1870 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001871 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001872 self.tgt = tgt
1873 self.src = src
1874 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001875 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001876 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001877
Tao Baodd2a5892015-03-12 12:32:37 -07001878 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001879 version = max(
1880 int(i) for i in
1881 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001882 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001883 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001884
1885 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001886 version=self.version,
1887 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001888 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001889 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001890 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001891 self.touched_src_ranges = b.touched_src_ranges
1892 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001893
Yifan Hong10c530d2018-12-27 17:34:18 -08001894 # On devices with dynamic partitions, for new partitions,
1895 # src is None but OPTIONS.source_info_dict is not.
1896 if OPTIONS.source_info_dict is None:
1897 is_dynamic_build = OPTIONS.info_dict.get(
1898 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001899 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001900 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001901 is_dynamic_build = OPTIONS.source_info_dict.get(
1902 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001903 is_dynamic_source = partition in shlex.split(
1904 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001905
Yifan Hongbb2658d2019-01-25 12:30:58 -08001906 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001907 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1908
Yifan Hongbb2658d2019-01-25 12:30:58 -08001909 # For dynamic partitions builds, check partition list in both source
1910 # and target build because new partitions may be added, and existing
1911 # partitions may be removed.
1912 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1913
Yifan Hong10c530d2018-12-27 17:34:18 -08001914 if is_dynamic:
1915 self.device = 'map_partition("%s")' % partition
1916 else:
1917 if OPTIONS.source_info_dict is None:
1918 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1919 else:
1920 _, device_path = GetTypeAndDevice("/" + partition,
1921 OPTIONS.source_info_dict)
1922 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001923
Tao Baod8d14be2016-02-04 14:26:02 -08001924 @property
1925 def required_cache(self):
1926 return self._required_cache
1927
Tao Bao76def242017-11-21 09:25:31 -08001928 def WriteScript(self, script, output_zip, progress=None,
1929 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001930 if not self.src:
1931 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001932 script.Print("Patching %s image unconditionally..." % (self.partition,))
1933 else:
1934 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001935
Dan Albert8b72aef2015-03-23 19:13:21 -07001936 if progress:
1937 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001938 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001939
1940 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001941 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001942
Tao Bao9bc6bb22015-11-09 16:58:28 -08001943 def WriteStrictVerifyScript(self, script):
1944 """Verify all the blocks in the care_map, including clobbered blocks.
1945
1946 This differs from the WriteVerifyScript() function: a) it prints different
1947 error messages; b) it doesn't allow half-way updated images to pass the
1948 verification."""
1949
1950 partition = self.partition
1951 script.Print("Verifying %s..." % (partition,))
1952 ranges = self.tgt.care_map
1953 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001954 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001955 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1956 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001957 self.device, ranges_str,
1958 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001959 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001960 script.AppendExtra("")
1961
Tao Baod522bdc2016-04-12 15:53:16 -07001962 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001963 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001964
1965 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001966 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001967 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001968
1969 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001970 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001971 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001972 ranges = self.touched_src_ranges
1973 expected_sha1 = self.touched_src_sha1
1974 else:
1975 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1976 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001977
1978 # No blocks to be checked, skipping.
1979 if not ranges:
1980 return
1981
Tao Bao5ece99d2015-05-12 11:42:31 -07001982 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001983 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001984 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001985 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1986 '"%s.patch.dat")) then' % (
1987 self.device, ranges_str, expected_sha1,
1988 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001989 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001990 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001991
Tianjie Xufc3422a2015-12-15 11:53:59 -08001992 if self.version >= 4:
1993
1994 # Bug: 21124327
1995 # When generating incrementals for the system and vendor partitions in
1996 # version 4 or newer, explicitly check the first block (which contains
1997 # the superblock) of the partition to see if it's what we expect. If
1998 # this check fails, give an explicit log message about the partition
1999 # having been remounted R/W (the most likely explanation).
2000 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002001 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002002
2003 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002004 if partition == "system":
2005 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2006 else:
2007 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002008 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002009 'ifelse (block_image_recover({device}, "{ranges}") && '
2010 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002011 'package_extract_file("{partition}.transfer.list"), '
2012 '"{partition}.new.dat", "{partition}.patch.dat"), '
2013 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002014 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002015 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002016 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002017
Tao Baodd2a5892015-03-12 12:32:37 -07002018 # Abort the OTA update. Note that the incremental OTA cannot be applied
2019 # even if it may match the checksum of the target partition.
2020 # a) If version < 3, operations like move and erase will make changes
2021 # unconditionally and damage the partition.
2022 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002023 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002024 if partition == "system":
2025 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2026 else:
2027 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2028 script.AppendExtra((
2029 'abort("E%d: %s partition has unexpected contents");\n'
2030 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002031
Yifan Hong10c530d2018-12-27 17:34:18 -08002032 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002033 partition = self.partition
2034 script.Print('Verifying the updated %s image...' % (partition,))
2035 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2036 ranges = self.tgt.care_map
2037 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002038 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002039 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002040 self.device, ranges_str,
2041 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002042
2043 # Bug: 20881595
2044 # Verify that extended blocks are really zeroed out.
2045 if self.tgt.extended:
2046 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002047 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002048 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002049 self.device, ranges_str,
2050 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002051 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002052 if partition == "system":
2053 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2054 else:
2055 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002056 script.AppendExtra(
2057 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002058 ' abort("E%d: %s partition has unexpected non-zero contents after '
2059 'OTA update");\n'
2060 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002061 else:
2062 script.Print('Verified the updated %s image.' % (partition,))
2063
Tianjie Xu209db462016-05-24 17:34:52 -07002064 if partition == "system":
2065 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2066 else:
2067 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2068
Tao Bao5fcaaef2015-06-01 13:40:49 -07002069 script.AppendExtra(
2070 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002071 ' abort("E%d: %s partition has unexpected contents after OTA '
2072 'update");\n'
2073 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002074
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002075 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002076 ZipWrite(output_zip,
2077 '{}.transfer.list'.format(self.path),
2078 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002079
Tao Bao76def242017-11-21 09:25:31 -08002080 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2081 # its size. Quailty 9 almost triples the compression time but doesn't
2082 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002083 # zip | brotli(quality 6) | brotli(quality 9)
2084 # compressed_size: 942M | 869M (~8% reduced) | 854M
2085 # compression_time: 75s | 265s | 719s
2086 # decompression_time: 15s | 25s | 25s
2087
2088 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002089 brotli_cmd = ['brotli', '--quality=6',
2090 '--output={}.new.dat.br'.format(self.path),
2091 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002092 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002093 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002094
2095 new_data_name = '{}.new.dat.br'.format(self.partition)
2096 ZipWrite(output_zip,
2097 '{}.new.dat.br'.format(self.path),
2098 new_data_name,
2099 compress_type=zipfile.ZIP_STORED)
2100 else:
2101 new_data_name = '{}.new.dat'.format(self.partition)
2102 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2103
Dan Albert8e0178d2015-01-27 15:53:15 -08002104 ZipWrite(output_zip,
2105 '{}.patch.dat'.format(self.path),
2106 '{}.patch.dat'.format(self.partition),
2107 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002108
Tianjie Xu209db462016-05-24 17:34:52 -07002109 if self.partition == "system":
2110 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2111 else:
2112 code = ErrorCode.VENDOR_UPDATE_FAILURE
2113
Yifan Hong10c530d2018-12-27 17:34:18 -08002114 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002115 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002116 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002117 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002118 device=self.device, partition=self.partition,
2119 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002120 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002121
Dan Albert8b72aef2015-03-23 19:13:21 -07002122 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002123 data = source.ReadRangeSet(ranges)
2124 ctx = sha1()
2125
2126 for p in data:
2127 ctx.update(p)
2128
2129 return ctx.hexdigest()
2130
Tao Baoe9b61912015-07-09 17:37:49 -07002131 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2132 """Return the hash value for all zero blocks."""
2133 zero_block = '\x00' * 4096
2134 ctx = sha1()
2135 for _ in range(num_blocks):
2136 ctx.update(zero_block)
2137
2138 return ctx.hexdigest()
2139
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002140
2141DataImage = blockimgdiff.DataImage
Yifan Hong8a66a712019-04-04 15:37:57 -07002142EmptyImage = blockimgdiff.EmptyImage
Tao Bao76def242017-11-21 09:25:31 -08002143
Doug Zongker96a57e72010-09-26 14:57:41 -07002144# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002145PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002146 "ext4": "EMMC",
2147 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002148 "f2fs": "EMMC",
2149 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002150}
Doug Zongker96a57e72010-09-26 14:57:41 -07002151
Tao Bao76def242017-11-21 09:25:31 -08002152
Doug Zongker96a57e72010-09-26 14:57:41 -07002153def GetTypeAndDevice(mount_point, info):
2154 fstab = info["fstab"]
2155 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002156 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2157 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002158 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002159 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002160
2161
2162def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002163 """Parses and converts a PEM-encoded certificate into DER-encoded.
2164
2165 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2166
2167 Returns:
2168 The decoded certificate string.
2169 """
2170 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002171 save = False
2172 for line in data.split("\n"):
2173 if "--END CERTIFICATE--" in line:
2174 break
2175 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002176 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002177 if "--BEGIN CERTIFICATE--" in line:
2178 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002179 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002180 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002181
Tao Bao04e1f012018-02-04 12:13:35 -08002182
2183def ExtractPublicKey(cert):
2184 """Extracts the public key (PEM-encoded) from the given certificate file.
2185
2186 Args:
2187 cert: The certificate filename.
2188
2189 Returns:
2190 The public key string.
2191
2192 Raises:
2193 AssertionError: On non-zero return from 'openssl'.
2194 """
2195 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2196 # While openssl 1.1 writes the key into the given filename followed by '-out',
2197 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2198 # stdout instead.
2199 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2200 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2201 pubkey, stderrdata = proc.communicate()
2202 assert proc.returncode == 0, \
2203 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2204 return pubkey
2205
2206
Tao Bao2cc0ca12019-03-15 10:44:43 -07002207def ExtractAvbPublicKey(key):
2208 """Extracts the AVB public key from the given public or private key.
2209
2210 Args:
2211 key: The input key file, which should be PEM-encoded public or private key.
2212
2213 Returns:
2214 The path to the extracted AVB public key file.
2215 """
2216 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2217 RunAndCheckOutput(
2218 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2219 return output
2220
2221
Doug Zongker412c02f2014-02-13 10:58:24 -08002222def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2223 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002224 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002225
Tao Bao6d5d6232018-03-09 17:04:42 -08002226 Most of the space in the boot and recovery images is just the kernel, which is
2227 identical for the two, so the resulting patch should be efficient. Add it to
2228 the output zip, along with a shell script that is run from init.rc on first
2229 boot to actually do the patching and install the new recovery image.
2230
2231 Args:
2232 input_dir: The top-level input directory of the target-files.zip.
2233 output_sink: The callback function that writes the result.
2234 recovery_img: File object for the recovery image.
2235 boot_img: File objects for the boot image.
2236 info_dict: A dict returned by common.LoadInfoDict() on the input
2237 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002238 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002239 if info_dict is None:
2240 info_dict = OPTIONS.info_dict
2241
Tao Bao6d5d6232018-03-09 17:04:42 -08002242 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002243
Tao Baof2cffbd2015-07-22 12:33:18 -07002244 if full_recovery_image:
2245 output_sink("etc/recovery.img", recovery_img.data)
2246
2247 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002248 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002249 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002250 # With system-root-image, boot and recovery images will have mismatching
2251 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2252 # to handle such a case.
2253 if system_root_image:
2254 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002255 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002256 assert not os.path.exists(path)
2257 else:
2258 diff_program = ["imgdiff"]
2259 if os.path.exists(path):
2260 diff_program.append("-b")
2261 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002262 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002263 else:
2264 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002265
2266 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2267 _, _, patch = d.ComputePatch()
2268 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002269
Dan Albertebb19aa2015-03-27 19:11:53 -07002270 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002271 # The following GetTypeAndDevice()s need to use the path in the target
2272 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002273 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2274 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2275 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002276 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002277
Tao Baof2cffbd2015-07-22 12:33:18 -07002278 if full_recovery_image:
2279 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002280if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2281 applypatch \\
2282 --flash /system/etc/recovery.img \\
2283 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2284 log -t recovery "Installing new recovery image: succeeded" || \\
2285 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002286else
2287 log -t recovery "Recovery image already installed"
2288fi
2289""" % {'type': recovery_type,
2290 'device': recovery_device,
2291 'sha1': recovery_img.sha1,
2292 'size': recovery_img.size}
2293 else:
2294 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002295if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2296 applypatch %(bonus_args)s \\
2297 --patch /system/recovery-from-boot.p \\
2298 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2299 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2300 log -t recovery "Installing new recovery image: succeeded" || \\
2301 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002302else
2303 log -t recovery "Recovery image already installed"
2304fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002305""" % {'boot_size': boot_img.size,
2306 'boot_sha1': boot_img.sha1,
2307 'recovery_size': recovery_img.size,
2308 'recovery_sha1': recovery_img.sha1,
2309 'boot_type': boot_type,
2310 'boot_device': boot_device,
2311 'recovery_type': recovery_type,
2312 'recovery_device': recovery_device,
2313 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002314
2315 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002316 # in the L release.
2317 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002318
Tao Bao32fcdab2018-10-12 10:30:39 -07002319 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002320
2321 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002322
2323
2324class DynamicPartitionUpdate(object):
2325 def __init__(self, src_group=None, tgt_group=None, progress=None,
2326 block_difference=None):
2327 self.src_group = src_group
2328 self.tgt_group = tgt_group
2329 self.progress = progress
2330 self.block_difference = block_difference
2331
2332 @property
2333 def src_size(self):
2334 if not self.block_difference:
2335 return 0
2336 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2337
2338 @property
2339 def tgt_size(self):
2340 if not self.block_difference:
2341 return 0
2342 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2343
2344 @staticmethod
2345 def _GetSparseImageSize(img):
2346 if not img:
2347 return 0
2348 return img.blocksize * img.total_blocks
2349
2350
2351class DynamicGroupUpdate(object):
2352 def __init__(self, src_size=None, tgt_size=None):
2353 # None: group does not exist. 0: no size limits.
2354 self.src_size = src_size
2355 self.tgt_size = tgt_size
2356
2357
2358class DynamicPartitionsDifference(object):
2359 def __init__(self, info_dict, block_diffs, progress_dict=None,
2360 source_info_dict=None):
2361 if progress_dict is None:
2362 progress_dict = dict()
2363
2364 self._remove_all_before_apply = False
2365 if source_info_dict is None:
2366 self._remove_all_before_apply = True
2367 source_info_dict = dict()
2368
2369 block_diff_dict = {e.partition:e for e in block_diffs}
2370 assert len(block_diff_dict) == len(block_diffs), \
2371 "Duplicated BlockDifference object for {}".format(
2372 [partition for partition, count in
2373 collections.Counter(e.partition for e in block_diffs).items()
2374 if count > 1])
2375
Yifan Hong79997e52019-01-23 16:56:19 -08002376 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002377
2378 for p, block_diff in block_diff_dict.items():
2379 self._partition_updates[p] = DynamicPartitionUpdate()
2380 self._partition_updates[p].block_difference = block_diff
2381
2382 for p, progress in progress_dict.items():
2383 if p in self._partition_updates:
2384 self._partition_updates[p].progress = progress
2385
2386 tgt_groups = shlex.split(info_dict.get(
2387 "super_partition_groups", "").strip())
2388 src_groups = shlex.split(source_info_dict.get(
2389 "super_partition_groups", "").strip())
2390
2391 for g in tgt_groups:
2392 for p in shlex.split(info_dict.get(
2393 "super_%s_partition_list" % g, "").strip()):
2394 assert p in self._partition_updates, \
2395 "{} is in target super_{}_partition_list but no BlockDifference " \
2396 "object is provided.".format(p, g)
2397 self._partition_updates[p].tgt_group = g
2398
2399 for g in src_groups:
2400 for p in shlex.split(source_info_dict.get(
2401 "super_%s_partition_list" % g, "").strip()):
2402 assert p in self._partition_updates, \
2403 "{} is in source super_{}_partition_list but no BlockDifference " \
2404 "object is provided.".format(p, g)
2405 self._partition_updates[p].src_group = g
2406
Yifan Hong45433e42019-01-18 13:55:25 -08002407 target_dynamic_partitions = set(shlex.split(info_dict.get(
2408 "dynamic_partition_list", "").strip()))
2409 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2410 if u.tgt_size)
2411 assert block_diffs_with_target == target_dynamic_partitions, \
2412 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2413 list(target_dynamic_partitions), list(block_diffs_with_target))
2414
2415 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2416 "dynamic_partition_list", "").strip()))
2417 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2418 if u.src_size)
2419 assert block_diffs_with_source == source_dynamic_partitions, \
2420 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2421 list(source_dynamic_partitions), list(block_diffs_with_source))
2422
Yifan Hong10c530d2018-12-27 17:34:18 -08002423 if self._partition_updates:
2424 logger.info("Updating dynamic partitions %s",
2425 self._partition_updates.keys())
2426
Yifan Hong79997e52019-01-23 16:56:19 -08002427 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002428
2429 for g in tgt_groups:
2430 self._group_updates[g] = DynamicGroupUpdate()
2431 self._group_updates[g].tgt_size = int(info_dict.get(
2432 "super_%s_group_size" % g, "0").strip())
2433
2434 for g in src_groups:
2435 if g not in self._group_updates:
2436 self._group_updates[g] = DynamicGroupUpdate()
2437 self._group_updates[g].src_size = int(source_info_dict.get(
2438 "super_%s_group_size" % g, "0").strip())
2439
2440 self._Compute()
2441
2442 def WriteScript(self, script, output_zip, write_verify_script=False):
2443 script.Comment('--- Start patching dynamic partitions ---')
2444 for p, u in self._partition_updates.items():
2445 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2446 script.Comment('Patch partition %s' % p)
2447 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2448 write_verify_script=False)
2449
2450 op_list_path = MakeTempFile()
2451 with open(op_list_path, 'w') as f:
2452 for line in self._op_list:
2453 f.write('{}\n'.format(line))
2454
2455 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2456
2457 script.Comment('Update dynamic partition metadata')
2458 script.AppendExtra('assert(update_dynamic_partitions('
2459 'package_extract_file("dynamic_partitions_op_list")));')
2460
2461 if write_verify_script:
2462 for p, u in self._partition_updates.items():
2463 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2464 u.block_difference.WritePostInstallVerifyScript(script)
2465 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2466
2467 for p, u in self._partition_updates.items():
2468 if u.tgt_size and u.src_size <= u.tgt_size:
2469 script.Comment('Patch partition %s' % p)
2470 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2471 write_verify_script=write_verify_script)
2472 if write_verify_script:
2473 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2474
2475 script.Comment('--- End patching dynamic partitions ---')
2476
2477 def _Compute(self):
2478 self._op_list = list()
2479
2480 def append(line):
2481 self._op_list.append(line)
2482
2483 def comment(line):
2484 self._op_list.append("# %s" % line)
2485
2486 if self._remove_all_before_apply:
2487 comment('Remove all existing dynamic partitions and groups before '
2488 'applying full OTA')
2489 append('remove_all_groups')
2490
2491 for p, u in self._partition_updates.items():
2492 if u.src_group and not u.tgt_group:
2493 append('remove %s' % p)
2494
2495 for p, u in self._partition_updates.items():
2496 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2497 comment('Move partition %s from %s to default' % (p, u.src_group))
2498 append('move %s default' % p)
2499
2500 for p, u in self._partition_updates.items():
2501 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2502 comment('Shrink partition %s from %d to %d' %
2503 (p, u.src_size, u.tgt_size))
2504 append('resize %s %s' % (p, u.tgt_size))
2505
2506 for g, u in self._group_updates.items():
2507 if u.src_size is not None and u.tgt_size is None:
2508 append('remove_group %s' % g)
2509 if (u.src_size is not None and u.tgt_size is not None and
2510 u.src_size > u.tgt_size):
2511 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2512 append('resize_group %s %d' % (g, u.tgt_size))
2513
2514 for g, u in self._group_updates.items():
2515 if u.src_size is None and u.tgt_size is not None:
2516 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2517 append('add_group %s %d' % (g, u.tgt_size))
2518 if (u.src_size is not None and u.tgt_size is not None and
2519 u.src_size < u.tgt_size):
2520 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2521 append('resize_group %s %d' % (g, u.tgt_size))
2522
2523 for p, u in self._partition_updates.items():
2524 if u.tgt_group and not u.src_group:
2525 comment('Add partition %s to group %s' % (p, u.tgt_group))
2526 append('add %s %s' % (p, u.tgt_group))
2527
2528 for p, u in self._partition_updates.items():
2529 if u.tgt_size and u.src_size < u.tgt_size:
2530 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2531 append('resize %s %d' % (p, u.tgt_size))
2532
2533 for p, u in self._partition_updates.items():
2534 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2535 comment('Move partition %s from default to %s' %
2536 (p, u.tgt_group))
2537 append('move %s %s' % (p, u.tgt_group))