blob: 632c1e2c2546089c9a7fa99f53eb76ab3c7e2dad [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Yifan Hong10c530d2018-12-27 17:34:18 -080017import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070018import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070019import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070020import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070021import getopt
22import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010023import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070024import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070025import json
26import logging
27import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070028import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080029import platform
Doug Zongkereef39442009-04-02 12:14:19 -070030import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070031import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070032import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080033import string
Doug Zongkereef39442009-04-02 12:14:19 -070034import subprocess
35import sys
36import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070037import threading
38import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070039import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080040from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070041
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070042import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070044
Tao Bao32fcdab2018-10-12 10:30:39 -070045logger = logging.getLogger(__name__)
46
Tao Bao986ee862018-10-04 15:46:16 -070047
Dan Albert8b72aef2015-03-23 19:13:21 -070048class Options(object):
49 def __init__(self):
Pavel Salomatov32676552019-03-06 20:00:45 +030050 base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
51 if base_out_path is None:
52 base_search_path = "out"
53 else:
Tao Bao2cc0ca12019-03-15 10:44:43 -070054 base_search_path = os.path.join(base_out_path,
55 os.path.basename(os.getcwd()))
Pavel Salomatov32676552019-03-06 20:00:45 +030056
Dan Albert8b72aef2015-03-23 19:13:21 -070057 platform_search_path = {
Pavel Salomatov32676552019-03-06 20:00:45 +030058 "linux2": os.path.join(base_search_path, "host/linux-x86"),
59 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070060 }
Doug Zongker85448772014-09-09 14:59:20 -070061
Tao Bao76def242017-11-21 09:25:31 -080062 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070063 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080064 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070065 self.extra_signapk_args = []
66 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080067 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070068 self.public_key_suffix = ".x509.pem"
69 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070070 # use otatools built boot_signer by default
71 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070072 self.boot_signer_args = []
73 self.verity_signer_path = None
74 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.verbose = False
76 self.tempfiles = []
77 self.device_specific = None
78 self.extras = {}
79 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070080 self.source_info_dict = None
81 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070082 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070083 # Stash size cannot exceed cache_size * threshold.
84 self.cache_size = None
85 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070086
87
88OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070089
Tao Bao71197512018-10-11 14:08:45 -070090# The block size that's used across the releasetools scripts.
91BLOCK_SIZE = 4096
92
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080093# Values for "certificate" in apkcerts that mean special things.
94SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
95
Tao Bao9dd909e2017-11-14 11:27:32 -080096# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010097AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010098 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080099
Tianjie Xu861f4132018-09-12 11:49:33 -0700100# Partitions that should have their care_map added to META/care_map.pb
101PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
102 'odm')
103
104
Tianjie Xu209db462016-05-24 17:34:52 -0700105class ErrorCode(object):
106 """Define error_codes for failures that happen during the actual
107 update package installation.
108
109 Error codes 0-999 are reserved for failures before the package
110 installation (i.e. low battery, package verification failure).
111 Detailed code in 'bootable/recovery/error_code.h' """
112
113 SYSTEM_VERIFICATION_FAILURE = 1000
114 SYSTEM_UPDATE_FAILURE = 1001
115 SYSTEM_UNEXPECTED_CONTENTS = 1002
116 SYSTEM_NONZERO_CONTENTS = 1003
117 SYSTEM_RECOVER_FAILURE = 1004
118 VENDOR_VERIFICATION_FAILURE = 2000
119 VENDOR_UPDATE_FAILURE = 2001
120 VENDOR_UNEXPECTED_CONTENTS = 2002
121 VENDOR_NONZERO_CONTENTS = 2003
122 VENDOR_RECOVER_FAILURE = 2004
123 OEM_PROP_MISMATCH = 3000
124 FINGERPRINT_MISMATCH = 3001
125 THUMBPRINT_MISMATCH = 3002
126 OLDER_BUILD = 3003
127 DEVICE_MISMATCH = 3004
128 BAD_PATCH_FILE = 3005
129 INSUFFICIENT_CACHE_SPACE = 3006
130 TUNE_PARTITION_FAILURE = 3007
131 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800132
Tao Bao80921982018-03-21 21:02:19 -0700133
Dan Albert8b72aef2015-03-23 19:13:21 -0700134class ExternalError(RuntimeError):
135 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700136
137
Tao Bao32fcdab2018-10-12 10:30:39 -0700138def InitLogging():
139 DEFAULT_LOGGING_CONFIG = {
140 'version': 1,
141 'disable_existing_loggers': False,
142 'formatters': {
143 'standard': {
144 'format':
145 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
146 'datefmt': '%Y-%m-%d %H:%M:%S',
147 },
148 },
149 'handlers': {
150 'default': {
151 'class': 'logging.StreamHandler',
152 'formatter': 'standard',
153 },
154 },
155 'loggers': {
156 '': {
157 'handlers': ['default'],
158 'level': 'WARNING',
159 'propagate': True,
160 }
161 }
162 }
163 env_config = os.getenv('LOGGING_CONFIG')
164 if env_config:
165 with open(env_config) as f:
166 config = json.load(f)
167 else:
168 config = DEFAULT_LOGGING_CONFIG
169
170 # Increase the logging level for verbose mode.
171 if OPTIONS.verbose:
172 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
173 config['loggers']['']['level'] = 'INFO'
174
175 logging.config.dictConfig(config)
176
177
Tao Bao39451582017-05-04 11:10:47 -0700178def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700179 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700180
Tao Bao73dd4f42018-10-04 16:25:33 -0700181 Args:
182 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700183 verbose: Whether the commands should be shown. Default to the global
184 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700185 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
186 stdin, etc. stdout and stderr will default to subprocess.PIPE and
187 subprocess.STDOUT respectively unless caller specifies any of them.
188
189 Returns:
190 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700191 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700192 if 'stdout' not in kwargs and 'stderr' not in kwargs:
193 kwargs['stdout'] = subprocess.PIPE
194 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700195 # Don't log any if caller explicitly says so.
196 if verbose != False:
197 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700198 return subprocess.Popen(args, **kwargs)
199
200
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800201def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800202 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800203
204 Args:
205 args: The command represented as a list of strings.
206 verbose: Whether the commands should be shown. Default to the global
207 verbosity if unspecified.
208 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
209 stdin, etc. stdout and stderr will default to subprocess.PIPE and
210 subprocess.STDOUT respectively unless caller specifies any of them.
211
Bill Peckham889b0c62019-02-21 18:53:37 -0800212 Raises:
213 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800214 """
215 proc = Run(args, verbose=verbose, **kwargs)
216 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800217
218 if proc.returncode != 0:
219 raise ExternalError(
220 "Failed to run command '{}' (exit code {})".format(
221 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800222
223
Tao Bao986ee862018-10-04 15:46:16 -0700224def RunAndCheckOutput(args, verbose=None, **kwargs):
225 """Runs the given command and returns the output.
226
227 Args:
228 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700229 verbose: Whether the commands should be shown. Default to the global
230 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700231 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
232 stdin, etc. stdout and stderr will default to subprocess.PIPE and
233 subprocess.STDOUT respectively unless caller specifies any of them.
234
235 Returns:
236 The output string.
237
238 Raises:
239 ExternalError: On non-zero exit from the command.
240 """
Tao Bao986ee862018-10-04 15:46:16 -0700241 proc = Run(args, verbose=verbose, **kwargs)
242 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700243 # Don't log any if caller explicitly says so.
244 if verbose != False:
245 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700246 if proc.returncode != 0:
247 raise ExternalError(
248 "Failed to run command '{}' (exit code {}):\n{}".format(
249 args, proc.returncode, output))
250 return output
251
252
Tao Baoc765cca2018-01-31 17:32:40 -0800253def RoundUpTo4K(value):
254 rounded_up = value + 4095
255 return rounded_up - (rounded_up % 4096)
256
257
Ying Wang7e6d4e42010-12-13 16:25:36 -0800258def CloseInheritedPipes():
259 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
260 before doing other work."""
261 if platform.system() != "Darwin":
262 return
263 for d in range(3, 1025):
264 try:
265 stat = os.fstat(d)
266 if stat is not None:
267 pipebit = stat[0] & 0x1000
268 if pipebit != 0:
269 os.close(d)
270 except OSError:
271 pass
272
273
Tao Bao410ad8b2018-08-24 12:08:38 -0700274def LoadInfoDict(input_file, repacking=False):
275 """Loads the key/value pairs from the given input target_files.
276
277 It reads `META/misc_info.txt` file in the target_files input, does sanity
278 checks and returns the parsed key/value pairs for to the given build. It's
279 usually called early when working on input target_files files, e.g. when
280 generating OTAs, or signing builds. Note that the function may be called
281 against an old target_files file (i.e. from past dessert releases). So the
282 property parsing needs to be backward compatible.
283
284 In a `META/misc_info.txt`, a few properties are stored as links to the files
285 in the PRODUCT_OUT directory. It works fine with the build system. However,
286 they are no longer available when (re)generating images from target_files zip.
287 When `repacking` is True, redirect these properties to the actual files in the
288 unzipped directory.
289
290 Args:
291 input_file: The input target_files file, which could be an open
292 zipfile.ZipFile instance, or a str for the dir that contains the files
293 unzipped from a target_files file.
294 repacking: Whether it's trying repack an target_files file after loading the
295 info dict (default: False). If so, it will rewrite a few loaded
296 properties (e.g. selinux_fc, root_dir) to point to the actual files in
297 target_files file. When doing repacking, `input_file` must be a dir.
298
299 Returns:
300 A dict that contains the parsed key/value pairs.
301
302 Raises:
303 AssertionError: On invalid input arguments.
304 ValueError: On malformed input values.
305 """
306 if repacking:
307 assert isinstance(input_file, str), \
308 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700309
Doug Zongkerc9253822014-02-04 12:17:58 -0800310 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700311 if isinstance(input_file, zipfile.ZipFile):
312 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800313 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700314 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800315 try:
316 with open(path) as f:
317 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700318 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800319 if e.errno == errno.ENOENT:
320 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800321
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700322 try:
Michael Runge6e836112014-04-15 17:40:21 -0700323 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700324 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700325 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700326
Tao Bao410ad8b2018-08-24 12:08:38 -0700327 if "recovery_api_version" not in d:
328 raise ValueError("Failed to find 'recovery_api_version'")
329 if "fstab_version" not in d:
330 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800331
Tao Bao410ad8b2018-08-24 12:08:38 -0700332 if repacking:
333 # We carry a copy of file_contexts.bin under META/. If not available, search
334 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
335 # images than the one running on device, in that case, we must have the one
336 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700337 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700338 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700339 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700340
Tom Cherryd14b8952018-08-09 14:26:00 -0700341 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700342
Tom Cherryd14b8952018-08-09 14:26:00 -0700343 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700344 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700345 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700346 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700347
Tao Baof54216f2016-03-29 15:12:37 -0700348 # Redirect {system,vendor}_base_fs_file.
349 if "system_base_fs_file" in d:
350 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700351 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700352 if os.path.exists(system_base_fs_file):
353 d["system_base_fs_file"] = system_base_fs_file
354 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700355 logger.warning(
356 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700357 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700358
359 if "vendor_base_fs_file" in d:
360 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700361 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700362 if os.path.exists(vendor_base_fs_file):
363 d["vendor_base_fs_file"] = vendor_base_fs_file
364 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700365 logger.warning(
366 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700367 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700368
Doug Zongker37974732010-09-16 17:44:38 -0700369 def makeint(key):
370 if key in d:
371 d[key] = int(d[key], 0)
372
373 makeint("recovery_api_version")
374 makeint("blocksize")
375 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700376 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700377 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700378 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700379 makeint("recovery_size")
380 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800381 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700382
Tao Baoa57ab9f2018-08-24 12:08:38 -0700383 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
384 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
385 # cases, since it may load the info_dict from an old build (e.g. when
386 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800387 system_root_image = d.get("system_root_image") == "true"
388 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700389 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700390 if isinstance(input_file, zipfile.ZipFile):
391 if recovery_fstab_path not in input_file.namelist():
392 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
393 else:
394 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
395 if not os.path.exists(path):
396 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800397 d["fstab"] = LoadRecoveryFSTab(
398 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700399
Tao Bao76def242017-11-21 09:25:31 -0800400 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700401 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700402 if isinstance(input_file, zipfile.ZipFile):
403 if recovery_fstab_path not in input_file.namelist():
404 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
405 else:
406 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
407 if not os.path.exists(path):
408 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800409 d["fstab"] = LoadRecoveryFSTab(
410 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700411
Tianjie Xucfa86222016-03-07 16:31:19 -0800412 else:
413 d["fstab"] = None
414
Tianjie Xu861f4132018-09-12 11:49:33 -0700415 # Tries to load the build props for all partitions with care_map, including
416 # system and vendor.
417 for partition in PARTITIONS_WITH_CARE_MAP:
418 d["{}.build.prop".format(partition)] = LoadBuildProp(
419 read_helper, "{}/build.prop".format(partition.upper()))
420 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800421
422 # Set up the salt (based on fingerprint or thumbprint) that will be used when
423 # adding AVB footer.
424 if d.get("avb_enable") == "true":
425 fp = None
426 if "build.prop" in d:
427 build_prop = d["build.prop"]
428 if "ro.build.fingerprint" in build_prop:
429 fp = build_prop["ro.build.fingerprint"]
430 elif "ro.build.thumbprint" in build_prop:
431 fp = build_prop["ro.build.thumbprint"]
432 if fp:
433 d["avb_salt"] = sha256(fp).hexdigest()
434
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700435 return d
436
Tao Baod1de6f32017-03-01 16:38:48 -0800437
Tao Baobcd1d162017-08-26 13:10:26 -0700438def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700439 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700440 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700441 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700442 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700443 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700444 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700445
Tao Baod1de6f32017-03-01 16:38:48 -0800446
Michael Runge6e836112014-04-15 17:40:21 -0700447def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700448 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700449 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700450 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700451 if not line or line.startswith("#"):
452 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700453 if "=" in line:
454 name, value = line.split("=", 1)
455 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700456 return d
457
Tao Baod1de6f32017-03-01 16:38:48 -0800458
Tianjie Xucfa86222016-03-07 16:31:19 -0800459def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
460 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700461 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800462 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700463 self.mount_point = mount_point
464 self.fs_type = fs_type
465 self.device = device
466 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700467 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700468
469 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800470 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700471 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700472 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700473 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700474
Tao Baod1de6f32017-03-01 16:38:48 -0800475 assert fstab_version == 2
476
477 d = {}
478 for line in data.split("\n"):
479 line = line.strip()
480 if not line or line.startswith("#"):
481 continue
482
483 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
484 pieces = line.split()
485 if len(pieces) != 5:
486 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
487
488 # Ignore entries that are managed by vold.
489 options = pieces[4]
490 if "voldmanaged=" in options:
491 continue
492
493 # It's a good line, parse it.
494 length = 0
495 options = options.split(",")
496 for i in options:
497 if i.startswith("length="):
498 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800499 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800500 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700501 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800502
Tao Baod1de6f32017-03-01 16:38:48 -0800503 mount_flags = pieces[3]
504 # Honor the SELinux context if present.
505 context = None
506 for i in mount_flags.split(","):
507 if i.startswith("context="):
508 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800509
Tao Baod1de6f32017-03-01 16:38:48 -0800510 mount_point = pieces[1]
511 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
512 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800513
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700514 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700515 # system. Other areas assume system is always at "/system" so point /system
516 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700517 if system_root_image:
518 assert not d.has_key("/system") and d.has_key("/")
519 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700520 return d
521
522
Doug Zongker37974732010-09-16 17:44:38 -0700523def DumpInfoDict(d):
524 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700525 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700526
Dan Albert8b72aef2015-03-23 19:13:21 -0700527
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800528def AppendAVBSigningArgs(cmd, partition):
529 """Append signing arguments for avbtool."""
530 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
531 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
532 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
533 if key_path and algorithm:
534 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700535 avb_salt = OPTIONS.info_dict.get("avb_salt")
536 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700537 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700538 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800539
540
Tao Bao02a08592018-07-22 12:40:45 -0700541def GetAvbChainedPartitionArg(partition, info_dict, key=None):
542 """Constructs and returns the arg to build or verify a chained partition.
543
544 Args:
545 partition: The partition name.
546 info_dict: The info dict to look up the key info and rollback index
547 location.
548 key: The key to be used for building or verifying the partition. Defaults to
549 the key listed in info_dict.
550
551 Returns:
552 A string of form "partition:rollback_index_location:key" that can be used to
553 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700554 """
555 if key is None:
556 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao2cc0ca12019-03-15 10:44:43 -0700557 pubkey_path = ExtractAvbPublicKey(key)
Tao Bao02a08592018-07-22 12:40:45 -0700558 rollback_index_location = info_dict[
559 "avb_" + partition + "_rollback_index_location"]
560 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
561
562
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700563def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800564 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700565 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700566
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700567 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800568 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
569 we are building a two-step special image (i.e. building a recovery image to
570 be loaded into /boot in two-step OTAs).
571
572 Return the image data, or None if sourcedir does not appear to contains files
573 for building the requested image.
574 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700575
576 def make_ramdisk():
577 ramdisk_img = tempfile.NamedTemporaryFile()
578
579 if os.access(fs_config_file, os.F_OK):
580 cmd = ["mkbootfs", "-f", fs_config_file,
581 os.path.join(sourcedir, "RAMDISK")]
582 else:
583 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
584 p1 = Run(cmd, stdout=subprocess.PIPE)
585 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
586
587 p2.wait()
588 p1.wait()
589 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
590 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
591
592 return ramdisk_img
593
594 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
595 return None
596
597 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700598 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700599
Doug Zongkerd5131602012-08-02 14:46:42 -0700600 if info_dict is None:
601 info_dict = OPTIONS.info_dict
602
Doug Zongkereef39442009-04-02 12:14:19 -0700603 img = tempfile.NamedTemporaryFile()
604
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700605 if has_ramdisk:
606 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700607
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800608 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
609 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
610
611 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700612
Benoit Fradina45a8682014-07-14 21:00:43 +0200613 fn = os.path.join(sourcedir, "second")
614 if os.access(fn, os.F_OK):
615 cmd.append("--second")
616 cmd.append(fn)
617
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800618 fn = os.path.join(sourcedir, "dtb")
619 if os.access(fn, os.F_OK):
620 cmd.append("--dtb")
621 cmd.append(fn)
622
Doug Zongker171f1cd2009-06-15 22:36:37 -0700623 fn = os.path.join(sourcedir, "cmdline")
624 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700625 cmd.append("--cmdline")
626 cmd.append(open(fn).read().rstrip("\n"))
627
628 fn = os.path.join(sourcedir, "base")
629 if os.access(fn, os.F_OK):
630 cmd.append("--base")
631 cmd.append(open(fn).read().rstrip("\n"))
632
Ying Wang4de6b5b2010-08-25 14:29:34 -0700633 fn = os.path.join(sourcedir, "pagesize")
634 if os.access(fn, os.F_OK):
635 cmd.append("--pagesize")
636 cmd.append(open(fn).read().rstrip("\n"))
637
Tao Bao76def242017-11-21 09:25:31 -0800638 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700639 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700640 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700641
Tao Bao76def242017-11-21 09:25:31 -0800642 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000643 if args and args.strip():
644 cmd.extend(shlex.split(args))
645
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700646 if has_ramdisk:
647 cmd.extend(["--ramdisk", ramdisk_img.name])
648
Tao Baod95e9fd2015-03-29 23:07:41 -0700649 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800650 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700651 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700652 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700653 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700654 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700655
Tao Baobf70c3182017-07-11 17:27:55 -0700656 # "boot" or "recovery", without extension.
657 partition_name = os.path.basename(sourcedir).lower()
658
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800659 if partition_name == "recovery":
660 if info_dict.get("include_recovery_dtbo") == "true":
661 fn = os.path.join(sourcedir, "recovery_dtbo")
662 cmd.extend(["--recovery_dtbo", fn])
663 if info_dict.get("include_recovery_acpio") == "true":
664 fn = os.path.join(sourcedir, "recovery_acpio")
665 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700666
Tao Bao986ee862018-10-04 15:46:16 -0700667 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700668
Tao Bao76def242017-11-21 09:25:31 -0800669 if (info_dict.get("boot_signer") == "true" and
670 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800671 # Hard-code the path as "/boot" for two-step special recovery image (which
672 # will be loaded into /boot during the two-step OTA).
673 if two_step_image:
674 path = "/boot"
675 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700676 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700677 cmd = [OPTIONS.boot_signer_path]
678 cmd.extend(OPTIONS.boot_signer_args)
679 cmd.extend([path, img.name,
680 info_dict["verity_key"] + ".pk8",
681 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700682 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700683
Tao Baod95e9fd2015-03-29 23:07:41 -0700684 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800685 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700686 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700687 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800688 # We have switched from the prebuilt futility binary to using the tool
689 # (futility-host) built from the source. Override the setting in the old
690 # TF.zip.
691 futility = info_dict["futility"]
692 if futility.startswith("prebuilts/"):
693 futility = "futility-host"
694 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700695 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700696 info_dict["vboot_key"] + ".vbprivk",
697 info_dict["vboot_subkey"] + ".vbprivk",
698 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700699 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700700 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700701
Tao Baof3282b42015-04-01 11:21:55 -0700702 # Clean up the temp files.
703 img_unsigned.close()
704 img_keyblock.close()
705
David Zeuthen8fecb282017-12-01 16:24:01 -0500706 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800707 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700708 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500709 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400710 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700711 "--partition_size", str(part_size), "--partition_name",
712 partition_name]
713 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500714 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400715 if args and args.strip():
716 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700717 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500718
719 img.seek(os.SEEK_SET, 0)
720 data = img.read()
721
722 if has_ramdisk:
723 ramdisk_img.close()
724 img.close()
725
726 return data
727
728
Doug Zongkerd5131602012-08-02 14:46:42 -0700729def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800730 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700731 """Return a File object with the desired bootable image.
732
733 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
734 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
735 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700736
Doug Zongker55d93282011-01-25 17:03:34 -0800737 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
738 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700739 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800740 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700741
742 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
743 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700744 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700745 return File.FromLocalFile(name, prebuilt_path)
746
Tao Bao32fcdab2018-10-12 10:30:39 -0700747 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700748
749 if info_dict is None:
750 info_dict = OPTIONS.info_dict
751
752 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800753 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
754 # for recovery.
755 has_ramdisk = (info_dict.get("system_root_image") != "true" or
756 prebuilt_name != "boot.img" or
757 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700758
Doug Zongker6f1d0312014-08-22 08:07:12 -0700759 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400760 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
761 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800762 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700763 if data:
764 return File(name, data)
765 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800766
Doug Zongkereef39442009-04-02 12:14:19 -0700767
Narayan Kamatha07bf042017-08-14 14:49:21 +0100768def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800769 """Gunzips the given gzip compressed file to a given output file."""
770 with gzip.open(in_filename, "rb") as in_file, \
771 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100772 shutil.copyfileobj(in_file, out_file)
773
774
Tao Bao0ff15de2019-03-20 11:26:06 -0700775def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800776 """Unzips the archive to the given directory.
777
778 Args:
779 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800780 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700781 patterns: Files to unzip from the archive. If omitted, will unzip the entire
782 archvie. Non-matching patterns will be filtered out. If there's no match
783 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800784 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800785 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700786 if patterns is not None:
787 # Filter out non-matching patterns. unzip will complain otherwise.
788 with zipfile.ZipFile(filename) as input_zip:
789 names = input_zip.namelist()
790 filtered = [
791 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
792
793 # There isn't any matching files. Don't unzip anything.
794 if not filtered:
795 return
796 cmd.extend(filtered)
797
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800798 RunAndCheckOutput(cmd)
799
800
Doug Zongker75f17362009-12-08 13:46:44 -0800801def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800802 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800803
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800804 Args:
805 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
806 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
807
808 pattern: Files to unzip from the archive. If omitted, will unzip the entire
809 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800810
Tao Bao1c830bf2017-12-25 10:43:47 -0800811 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800812 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800813 """
Doug Zongkereef39442009-04-02 12:14:19 -0700814
Tao Bao1c830bf2017-12-25 10:43:47 -0800815 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800816 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
817 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800818 UnzipToDir(m.group(1), tmp, pattern)
819 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800820 filename = m.group(1)
821 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800822 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800823
Tao Baodba59ee2018-01-09 13:21:02 -0800824 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700825
826
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700827def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
828 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800829 """Returns a SparseImage object suitable for passing to BlockImageDiff.
830
831 This function loads the specified sparse image from the given path, and
832 performs additional processing for OTA purpose. For example, it always adds
833 block 0 to clobbered blocks list. It also detects files that cannot be
834 reconstructed from the block list, for whom we should avoid applying imgdiff.
835
836 Args:
837 which: The partition name, which must be "system" or "vendor".
838 tmpdir: The directory that contains the prebuilt image and block map file.
839 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800840 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700841 hashtree_info_generator: If present, generates the hashtree_info for this
842 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800843 Returns:
844 A SparseImage object, with file_map info loaded.
845 """
846 assert which in ("system", "vendor")
847
848 path = os.path.join(tmpdir, "IMAGES", which + ".img")
849 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
850
851 # The image and map files must have been created prior to calling
852 # ota_from_target_files.py (since LMP).
853 assert os.path.exists(path) and os.path.exists(mappath)
854
855 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
856 # it to clobbered_blocks so that it will be written to the target
857 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
858 clobbered_blocks = "0"
859
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700860 image = sparse_img.SparseImage(
861 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
862 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800863
864 # block.map may contain less blocks, because mke2fs may skip allocating blocks
865 # if they contain all zeros. We can't reconstruct such a file from its block
866 # list. Tag such entries accordingly. (Bug: 65213616)
867 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800868 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700869 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800870 continue
871
Tom Cherryd14b8952018-08-09 14:26:00 -0700872 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
873 # filename listed in system.map may contain an additional leading slash
874 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
875 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700876 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
877
Tom Cherryd14b8952018-08-09 14:26:00 -0700878 # Special handling another case, where files not under /system
879 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700880 if which == 'system' and not arcname.startswith('SYSTEM'):
881 arcname = 'ROOT/' + arcname
882
883 assert arcname in input_zip.namelist(), \
884 "Failed to find the ZIP entry for {}".format(entry)
885
Tao Baoc765cca2018-01-31 17:32:40 -0800886 info = input_zip.getinfo(arcname)
887 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800888
889 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800890 # image, check the original block list to determine its completeness. Note
891 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800892 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800893 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800894
Tao Baoc765cca2018-01-31 17:32:40 -0800895 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
896 ranges.extra['incomplete'] = True
897
898 return image
899
900
Doug Zongkereef39442009-04-02 12:14:19 -0700901def GetKeyPasswords(keylist):
902 """Given a list of keys, prompt the user to enter passwords for
903 those which require them. Return a {key: password} dict. password
904 will be None if the key has no password."""
905
Doug Zongker8ce7c252009-05-22 13:34:54 -0700906 no_passwords = []
907 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700908 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700909 devnull = open("/dev/null", "w+b")
910 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800911 # We don't need a password for things that aren't really keys.
912 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700913 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700914 continue
915
T.R. Fullhart37e10522013-03-18 10:31:26 -0700916 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700917 "-inform", "DER", "-nocrypt"],
918 stdin=devnull.fileno(),
919 stdout=devnull.fileno(),
920 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700921 p.communicate()
922 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700923 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700924 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700925 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700926 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
927 "-inform", "DER", "-passin", "pass:"],
928 stdin=devnull.fileno(),
929 stdout=devnull.fileno(),
930 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700931 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700932 if p.returncode == 0:
933 # Encrypted key with empty string as password.
934 key_passwords[k] = ''
935 elif stderr.startswith('Error decrypting key'):
936 # Definitely encrypted key.
937 # It would have said "Error reading key" if it didn't parse correctly.
938 need_passwords.append(k)
939 else:
940 # Potentially, a type of key that openssl doesn't understand.
941 # We'll let the routines in signapk.jar handle it.
942 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700943 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700944
T.R. Fullhart37e10522013-03-18 10:31:26 -0700945 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800946 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700947 return key_passwords
948
949
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800950def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700951 """Gets the minSdkVersion declared in the APK.
952
953 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
954 This can be both a decimal number (API Level) or a codename.
955
956 Args:
957 apk_name: The APK filename.
958
959 Returns:
960 The parsed SDK version string.
961
962 Raises:
963 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800964 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700965 proc = Run(
966 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
967 stderr=subprocess.PIPE)
968 stdoutdata, stderrdata = proc.communicate()
969 if proc.returncode != 0:
970 raise ExternalError(
971 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
972 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800973
Tao Baof47bf0f2018-03-21 23:28:51 -0700974 for line in stdoutdata.split("\n"):
975 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800976 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
977 if m:
978 return m.group(1)
979 raise ExternalError("No minSdkVersion returned by aapt")
980
981
982def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700983 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800984
Tao Baof47bf0f2018-03-21 23:28:51 -0700985 If minSdkVersion is set to a codename, it is translated to a number using the
986 provided map.
987
988 Args:
989 apk_name: The APK filename.
990
991 Returns:
992 The parsed SDK version number.
993
994 Raises:
995 ExternalError: On failing to get the min SDK version number.
996 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800997 version = GetMinSdkVersion(apk_name)
998 try:
999 return int(version)
1000 except ValueError:
1001 # Not a decimal number. Codename?
1002 if version in codename_to_api_level_map:
1003 return codename_to_api_level_map[version]
1004 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001005 raise ExternalError(
1006 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1007 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001008
1009
1010def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001011 codename_to_api_level_map=None, whole_file=False,
1012 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001013 """Sign the input_name zip/jar/apk, producing output_name. Use the
1014 given key and password (the latter may be None if the key does not
1015 have a password.
1016
Doug Zongker951495f2009-08-14 12:44:19 -07001017 If whole_file is true, use the "-w" option to SignApk to embed a
1018 signature that covers the whole file in the archive comment of the
1019 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001020
1021 min_api_level is the API Level (int) of the oldest platform this file may end
1022 up on. If not specified for an APK, the API Level is obtained by interpreting
1023 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1024
1025 codename_to_api_level_map is needed to translate the codename which may be
1026 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001027
1028 Caller may optionally specify extra args to be passed to SignApk, which
1029 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001030 """
Tao Bao76def242017-11-21 09:25:31 -08001031 if codename_to_api_level_map is None:
1032 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001033 if extra_signapk_args is None:
1034 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001035
Alex Klyubin9667b182015-12-10 13:38:50 -08001036 java_library_path = os.path.join(
1037 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1038
Tao Baoe95540e2016-11-08 12:08:53 -08001039 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1040 ["-Djava.library.path=" + java_library_path,
1041 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001042 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001043 if whole_file:
1044 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001045
1046 min_sdk_version = min_api_level
1047 if min_sdk_version is None:
1048 if not whole_file:
1049 min_sdk_version = GetMinSdkVersionInt(
1050 input_name, codename_to_api_level_map)
1051 if min_sdk_version is not None:
1052 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1053
T.R. Fullhart37e10522013-03-18 10:31:26 -07001054 cmd.extend([key + OPTIONS.public_key_suffix,
1055 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001056 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001057
Tao Bao73dd4f42018-10-04 16:25:33 -07001058 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001059 if password is not None:
1060 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001061 stdoutdata, _ = proc.communicate(password)
1062 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001063 raise ExternalError(
1064 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001065 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001066
Doug Zongkereef39442009-04-02 12:14:19 -07001067
Doug Zongker37974732010-09-16 17:44:38 -07001068def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001069 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001070
Tao Bao9dd909e2017-11-14 11:27:32 -08001071 For non-AVB images, raise exception if the data is too big. Print a warning
1072 if the data is nearing the maximum size.
1073
1074 For AVB images, the actual image size should be identical to the limit.
1075
1076 Args:
1077 data: A string that contains all the data for the partition.
1078 target: The partition name. The ".img" suffix is optional.
1079 info_dict: The dict to be looked up for relevant info.
1080 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001081 if target.endswith(".img"):
1082 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001083 mount_point = "/" + target
1084
Ying Wangf8824af2014-06-03 14:07:27 -07001085 fs_type = None
1086 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001087 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001088 if mount_point == "/userdata":
1089 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001090 p = info_dict["fstab"][mount_point]
1091 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001092 device = p.device
1093 if "/" in device:
1094 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001095 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001096 if not fs_type or not limit:
1097 return
Doug Zongkereef39442009-04-02 12:14:19 -07001098
Andrew Boie0f9aec82012-02-14 09:32:52 -08001099 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001100 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1101 # path.
1102 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1103 if size != limit:
1104 raise ExternalError(
1105 "Mismatching image size for %s: expected %d actual %d" % (
1106 target, limit, size))
1107 else:
1108 pct = float(size) * 100.0 / limit
1109 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1110 if pct >= 99.0:
1111 raise ExternalError(msg)
1112 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001113 logger.warning("\n WARNING: %s\n", msg)
1114 else:
1115 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001116
1117
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001118def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001119 """Parses the APK certs info from a given target-files zip.
1120
1121 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1122 tuple with the following elements: (1) a dictionary that maps packages to
1123 certs (based on the "certificate" and "private_key" attributes in the file;
1124 (2) a string representing the extension of compressed APKs in the target files
1125 (e.g ".gz", ".bro").
1126
1127 Args:
1128 tf_zip: The input target_files ZipFile (already open).
1129
1130 Returns:
1131 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1132 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1133 no compressed APKs.
1134 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001135 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001136 compressed_extension = None
1137
Tao Bao0f990332017-09-08 19:02:54 -07001138 # META/apkcerts.txt contains the info for _all_ the packages known at build
1139 # time. Filter out the ones that are not installed.
1140 installed_files = set()
1141 for name in tf_zip.namelist():
1142 basename = os.path.basename(name)
1143 if basename:
1144 installed_files.add(basename)
1145
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001146 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1147 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001148 if not line:
1149 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001150 m = re.match(
1151 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1152 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1153 line)
1154 if not m:
1155 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001156
Tao Bao818ddf52018-01-05 11:17:34 -08001157 matches = m.groupdict()
1158 cert = matches["CERT"]
1159 privkey = matches["PRIVKEY"]
1160 name = matches["NAME"]
1161 this_compressed_extension = matches["COMPRESSED"]
1162
1163 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1164 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1165 if cert in SPECIAL_CERT_STRINGS and not privkey:
1166 certmap[name] = cert
1167 elif (cert.endswith(OPTIONS.public_key_suffix) and
1168 privkey.endswith(OPTIONS.private_key_suffix) and
1169 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1170 certmap[name] = cert[:-public_key_suffix_len]
1171 else:
1172 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1173
1174 if not this_compressed_extension:
1175 continue
1176
1177 # Only count the installed files.
1178 filename = name + '.' + this_compressed_extension
1179 if filename not in installed_files:
1180 continue
1181
1182 # Make sure that all the values in the compression map have the same
1183 # extension. We don't support multiple compression methods in the same
1184 # system image.
1185 if compressed_extension:
1186 if this_compressed_extension != compressed_extension:
1187 raise ValueError(
1188 "Multiple compressed extensions: {} vs {}".format(
1189 compressed_extension, this_compressed_extension))
1190 else:
1191 compressed_extension = this_compressed_extension
1192
1193 return (certmap,
1194 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001195
1196
Doug Zongkereef39442009-04-02 12:14:19 -07001197COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001198Global options
1199
1200 -p (--path) <dir>
1201 Prepend <dir>/bin to the list of places to search for binaries run by this
1202 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001203
Doug Zongker05d3dea2009-06-22 11:32:31 -07001204 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001205 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001206
Tao Bao30df8b42018-04-23 15:32:53 -07001207 -x (--extra) <key=value>
1208 Add a key/value pair to the 'extras' dict, which device-specific extension
1209 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001210
Doug Zongkereef39442009-04-02 12:14:19 -07001211 -v (--verbose)
1212 Show command lines being executed.
1213
1214 -h (--help)
1215 Display this usage message and exit.
1216"""
1217
1218def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001219 print(docstring.rstrip("\n"))
1220 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001221
1222
1223def ParseOptions(argv,
1224 docstring,
1225 extra_opts="", extra_long_opts=(),
1226 extra_option_handler=None):
1227 """Parse the options in argv and return any arguments that aren't
1228 flags. docstring is the calling module's docstring, to be displayed
1229 for errors and -h. extra_opts and extra_long_opts are for flags
1230 defined by the caller, which are processed by passing them to
1231 extra_option_handler."""
1232
1233 try:
1234 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001235 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001236 ["help", "verbose", "path=", "signapk_path=",
1237 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001238 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001239 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1240 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001241 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001242 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001243 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001244 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001245 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001246 sys.exit(2)
1247
Doug Zongkereef39442009-04-02 12:14:19 -07001248 for o, a in opts:
1249 if o in ("-h", "--help"):
1250 Usage(docstring)
1251 sys.exit()
1252 elif o in ("-v", "--verbose"):
1253 OPTIONS.verbose = True
1254 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001255 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001256 elif o in ("--signapk_path",):
1257 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001258 elif o in ("--signapk_shared_library_path",):
1259 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001260 elif o in ("--extra_signapk_args",):
1261 OPTIONS.extra_signapk_args = shlex.split(a)
1262 elif o in ("--java_path",):
1263 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001264 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001265 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001266 elif o in ("--public_key_suffix",):
1267 OPTIONS.public_key_suffix = a
1268 elif o in ("--private_key_suffix",):
1269 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001270 elif o in ("--boot_signer_path",):
1271 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001272 elif o in ("--boot_signer_args",):
1273 OPTIONS.boot_signer_args = shlex.split(a)
1274 elif o in ("--verity_signer_path",):
1275 OPTIONS.verity_signer_path = a
1276 elif o in ("--verity_signer_args",):
1277 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001278 elif o in ("-s", "--device_specific"):
1279 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001280 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001281 key, value = a.split("=", 1)
1282 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001283 else:
1284 if extra_option_handler is None or not extra_option_handler(o, a):
1285 assert False, "unknown option \"%s\"" % (o,)
1286
Doug Zongker85448772014-09-09 14:59:20 -07001287 if OPTIONS.search_path:
1288 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1289 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001290
1291 return args
1292
1293
Tao Bao4c851b12016-09-19 13:54:38 -07001294def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001295 """Make a temp file and add it to the list of things to be deleted
1296 when Cleanup() is called. Return the filename."""
1297 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1298 os.close(fd)
1299 OPTIONS.tempfiles.append(fn)
1300 return fn
1301
1302
Tao Bao1c830bf2017-12-25 10:43:47 -08001303def MakeTempDir(prefix='tmp', suffix=''):
1304 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1305
1306 Returns:
1307 The absolute pathname of the new directory.
1308 """
1309 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1310 OPTIONS.tempfiles.append(dir_name)
1311 return dir_name
1312
1313
Doug Zongkereef39442009-04-02 12:14:19 -07001314def Cleanup():
1315 for i in OPTIONS.tempfiles:
1316 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001317 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001318 else:
1319 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001320 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001321
1322
1323class PasswordManager(object):
1324 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001325 self.editor = os.getenv("EDITOR")
1326 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001327
1328 def GetPasswords(self, items):
1329 """Get passwords corresponding to each string in 'items',
1330 returning a dict. (The dict may have keys in addition to the
1331 values in 'items'.)
1332
1333 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1334 user edit that file to add more needed passwords. If no editor is
1335 available, or $ANDROID_PW_FILE isn't define, prompts the user
1336 interactively in the ordinary way.
1337 """
1338
1339 current = self.ReadFile()
1340
1341 first = True
1342 while True:
1343 missing = []
1344 for i in items:
1345 if i not in current or not current[i]:
1346 missing.append(i)
1347 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001348 if not missing:
1349 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001350
1351 for i in missing:
1352 current[i] = ""
1353
1354 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001355 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001356 answer = raw_input("try to edit again? [y]> ").strip()
1357 if answer and answer[0] not in 'yY':
1358 raise RuntimeError("key passwords unavailable")
1359 first = False
1360
1361 current = self.UpdateAndReadFile(current)
1362
Dan Albert8b72aef2015-03-23 19:13:21 -07001363 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001364 """Prompt the user to enter a value (password) for each key in
1365 'current' whose value is fales. Returns a new dict with all the
1366 values.
1367 """
1368 result = {}
1369 for k, v in sorted(current.iteritems()):
1370 if v:
1371 result[k] = v
1372 else:
1373 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001374 result[k] = getpass.getpass(
1375 "Enter password for %s key> " % k).strip()
1376 if result[k]:
1377 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001378 return result
1379
1380 def UpdateAndReadFile(self, current):
1381 if not self.editor or not self.pwfile:
1382 return self.PromptResult(current)
1383
1384 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001385 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001386 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1387 f.write("# (Additional spaces are harmless.)\n\n")
1388
1389 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001390 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1391 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001392 f.write("[[[ %s ]]] %s\n" % (v, k))
1393 if not v and first_line is None:
1394 # position cursor on first line with no password.
1395 first_line = i + 4
1396 f.close()
1397
Tao Bao986ee862018-10-04 15:46:16 -07001398 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001399
1400 return self.ReadFile()
1401
1402 def ReadFile(self):
1403 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001404 if self.pwfile is None:
1405 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001406 try:
1407 f = open(self.pwfile, "r")
1408 for line in f:
1409 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001410 if not line or line[0] == '#':
1411 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001412 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1413 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001414 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001415 else:
1416 result[m.group(2)] = m.group(1)
1417 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001418 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001419 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001420 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001421 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001422
1423
Dan Albert8e0178d2015-01-27 15:53:15 -08001424def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1425 compress_type=None):
1426 import datetime
1427
1428 # http://b/18015246
1429 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1430 # for files larger than 2GiB. We can work around this by adjusting their
1431 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1432 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1433 # it isn't clear to me exactly what circumstances cause this).
1434 # `zipfile.write()` must be used directly to work around this.
1435 #
1436 # This mess can be avoided if we port to python3.
1437 saved_zip64_limit = zipfile.ZIP64_LIMIT
1438 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1439
1440 if compress_type is None:
1441 compress_type = zip_file.compression
1442 if arcname is None:
1443 arcname = filename
1444
1445 saved_stat = os.stat(filename)
1446
1447 try:
1448 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1449 # file to be zipped and reset it when we're done.
1450 os.chmod(filename, perms)
1451
1452 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001453 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1454 # intentional. zip stores datetimes in local time without a time zone
1455 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1456 # in the zip archive.
1457 local_epoch = datetime.datetime.fromtimestamp(0)
1458 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001459 os.utime(filename, (timestamp, timestamp))
1460
1461 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1462 finally:
1463 os.chmod(filename, saved_stat.st_mode)
1464 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1465 zipfile.ZIP64_LIMIT = saved_zip64_limit
1466
1467
Tao Bao58c1b962015-05-20 09:32:18 -07001468def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001469 compress_type=None):
1470 """Wrap zipfile.writestr() function to work around the zip64 limit.
1471
1472 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1473 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1474 when calling crc32(bytes).
1475
1476 But it still works fine to write a shorter string into a large zip file.
1477 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1478 when we know the string won't be too long.
1479 """
1480
1481 saved_zip64_limit = zipfile.ZIP64_LIMIT
1482 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1483
1484 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1485 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001486 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001487 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001488 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001489 else:
Tao Baof3282b42015-04-01 11:21:55 -07001490 zinfo = zinfo_or_arcname
1491
1492 # If compress_type is given, it overrides the value in zinfo.
1493 if compress_type is not None:
1494 zinfo.compress_type = compress_type
1495
Tao Bao58c1b962015-05-20 09:32:18 -07001496 # If perms is given, it has a priority.
1497 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001498 # If perms doesn't set the file type, mark it as a regular file.
1499 if perms & 0o770000 == 0:
1500 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001501 zinfo.external_attr = perms << 16
1502
Tao Baof3282b42015-04-01 11:21:55 -07001503 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001504 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1505
Dan Albert8b72aef2015-03-23 19:13:21 -07001506 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001507 zipfile.ZIP64_LIMIT = saved_zip64_limit
1508
1509
Tao Bao89d7ab22017-12-14 17:05:33 -08001510def ZipDelete(zip_filename, entries):
1511 """Deletes entries from a ZIP file.
1512
1513 Since deleting entries from a ZIP file is not supported, it shells out to
1514 'zip -d'.
1515
1516 Args:
1517 zip_filename: The name of the ZIP file.
1518 entries: The name of the entry, or the list of names to be deleted.
1519
1520 Raises:
1521 AssertionError: In case of non-zero return from 'zip'.
1522 """
1523 if isinstance(entries, basestring):
1524 entries = [entries]
1525 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001526 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001527
1528
Tao Baof3282b42015-04-01 11:21:55 -07001529def ZipClose(zip_file):
1530 # http://b/18015246
1531 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1532 # central directory.
1533 saved_zip64_limit = zipfile.ZIP64_LIMIT
1534 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1535
1536 zip_file.close()
1537
1538 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001539
1540
1541class DeviceSpecificParams(object):
1542 module = None
1543 def __init__(self, **kwargs):
1544 """Keyword arguments to the constructor become attributes of this
1545 object, which is passed to all functions in the device-specific
1546 module."""
1547 for k, v in kwargs.iteritems():
1548 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001549 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001550
1551 if self.module is None:
1552 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001553 if not path:
1554 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001555 try:
1556 if os.path.isdir(path):
1557 info = imp.find_module("releasetools", [path])
1558 else:
1559 d, f = os.path.split(path)
1560 b, x = os.path.splitext(f)
1561 if x == ".py":
1562 f = b
1563 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001564 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001565 self.module = imp.load_module("device_specific", *info)
1566 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001567 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001568
1569 def _DoCall(self, function_name, *args, **kwargs):
1570 """Call the named function in the device-specific module, passing
1571 the given args and kwargs. The first argument to the call will be
1572 the DeviceSpecific object itself. If there is no module, or the
1573 module does not define the function, return the value of the
1574 'default' kwarg (which itself defaults to None)."""
1575 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001576 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001577 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1578
1579 def FullOTA_Assertions(self):
1580 """Called after emitting the block of assertions at the top of a
1581 full OTA package. Implementations can add whatever additional
1582 assertions they like."""
1583 return self._DoCall("FullOTA_Assertions")
1584
Doug Zongkere5ff5902012-01-17 10:55:37 -08001585 def FullOTA_InstallBegin(self):
1586 """Called at the start of full OTA installation."""
1587 return self._DoCall("FullOTA_InstallBegin")
1588
Yifan Hong10c530d2018-12-27 17:34:18 -08001589 def FullOTA_GetBlockDifferences(self):
1590 """Called during full OTA installation and verification.
1591 Implementation should return a list of BlockDifference objects describing
1592 the update on each additional partitions.
1593 """
1594 return self._DoCall("FullOTA_GetBlockDifferences")
1595
Doug Zongker05d3dea2009-06-22 11:32:31 -07001596 def FullOTA_InstallEnd(self):
1597 """Called at the end of full OTA installation; typically this is
1598 used to install the image for the device's baseband processor."""
1599 return self._DoCall("FullOTA_InstallEnd")
1600
1601 def IncrementalOTA_Assertions(self):
1602 """Called after emitting the block of assertions at the top of an
1603 incremental OTA package. Implementations can add whatever
1604 additional assertions they like."""
1605 return self._DoCall("IncrementalOTA_Assertions")
1606
Doug Zongkere5ff5902012-01-17 10:55:37 -08001607 def IncrementalOTA_VerifyBegin(self):
1608 """Called at the start of the verification phase of incremental
1609 OTA installation; additional checks can be placed here to abort
1610 the script before any changes are made."""
1611 return self._DoCall("IncrementalOTA_VerifyBegin")
1612
Doug Zongker05d3dea2009-06-22 11:32:31 -07001613 def IncrementalOTA_VerifyEnd(self):
1614 """Called at the end of the verification phase of incremental OTA
1615 installation; additional checks can be placed here to abort the
1616 script before any changes are made."""
1617 return self._DoCall("IncrementalOTA_VerifyEnd")
1618
Doug Zongkere5ff5902012-01-17 10:55:37 -08001619 def IncrementalOTA_InstallBegin(self):
1620 """Called at the start of incremental OTA installation (after
1621 verification is complete)."""
1622 return self._DoCall("IncrementalOTA_InstallBegin")
1623
Yifan Hong10c530d2018-12-27 17:34:18 -08001624 def IncrementalOTA_GetBlockDifferences(self):
1625 """Called during incremental OTA installation and verification.
1626 Implementation should return a list of BlockDifference objects describing
1627 the update on each additional partitions.
1628 """
1629 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1630
Doug Zongker05d3dea2009-06-22 11:32:31 -07001631 def IncrementalOTA_InstallEnd(self):
1632 """Called at the end of incremental OTA installation; typically
1633 this is used to install the image for the device's baseband
1634 processor."""
1635 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001636
Tao Bao9bc6bb22015-11-09 16:58:28 -08001637 def VerifyOTA_Assertions(self):
1638 return self._DoCall("VerifyOTA_Assertions")
1639
Tao Bao76def242017-11-21 09:25:31 -08001640
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001641class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001642 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001643 self.name = name
1644 self.data = data
1645 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001646 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001647 self.sha1 = sha1(data).hexdigest()
1648
1649 @classmethod
1650 def FromLocalFile(cls, name, diskname):
1651 f = open(diskname, "rb")
1652 data = f.read()
1653 f.close()
1654 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001655
1656 def WriteToTemp(self):
1657 t = tempfile.NamedTemporaryFile()
1658 t.write(self.data)
1659 t.flush()
1660 return t
1661
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001662 def WriteToDir(self, d):
1663 with open(os.path.join(d, self.name), "wb") as fp:
1664 fp.write(self.data)
1665
Geremy Condra36bd3652014-02-06 19:45:10 -08001666 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001667 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001668
Tao Bao76def242017-11-21 09:25:31 -08001669
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001670DIFF_PROGRAM_BY_EXT = {
1671 ".gz" : "imgdiff",
1672 ".zip" : ["imgdiff", "-z"],
1673 ".jar" : ["imgdiff", "-z"],
1674 ".apk" : ["imgdiff", "-z"],
1675 ".img" : "imgdiff",
1676 }
1677
Tao Bao76def242017-11-21 09:25:31 -08001678
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001679class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001680 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001681 self.tf = tf
1682 self.sf = sf
1683 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001684 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001685
1686 def ComputePatch(self):
1687 """Compute the patch (as a string of data) needed to turn sf into
1688 tf. Returns the same tuple as GetPatch()."""
1689
1690 tf = self.tf
1691 sf = self.sf
1692
Doug Zongker24cd2802012-08-14 16:36:15 -07001693 if self.diff_program:
1694 diff_program = self.diff_program
1695 else:
1696 ext = os.path.splitext(tf.name)[1]
1697 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001698
1699 ttemp = tf.WriteToTemp()
1700 stemp = sf.WriteToTemp()
1701
1702 ext = os.path.splitext(tf.name)[1]
1703
1704 try:
1705 ptemp = tempfile.NamedTemporaryFile()
1706 if isinstance(diff_program, list):
1707 cmd = copy.copy(diff_program)
1708 else:
1709 cmd = [diff_program]
1710 cmd.append(stemp.name)
1711 cmd.append(ttemp.name)
1712 cmd.append(ptemp.name)
1713 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001714 err = []
1715 def run():
1716 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001717 if e:
1718 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001719 th = threading.Thread(target=run)
1720 th.start()
1721 th.join(timeout=300) # 5 mins
1722 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001723 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001724 p.terminate()
1725 th.join(5)
1726 if th.is_alive():
1727 p.kill()
1728 th.join()
1729
Tianjie Xua2a9f992018-01-05 15:15:54 -08001730 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001731 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001732 self.patch = None
1733 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001734 diff = ptemp.read()
1735 finally:
1736 ptemp.close()
1737 stemp.close()
1738 ttemp.close()
1739
1740 self.patch = diff
1741 return self.tf, self.sf, self.patch
1742
1743
1744 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001745 """Returns a tuple of (target_file, source_file, patch_data).
1746
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001747 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001748 computing the patch failed.
1749 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001750 return self.tf, self.sf, self.patch
1751
1752
1753def ComputeDifferences(diffs):
1754 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001755 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001756
1757 # Do the largest files first, to try and reduce the long-pole effect.
1758 by_size = [(i.tf.size, i) for i in diffs]
1759 by_size.sort(reverse=True)
1760 by_size = [i[1] for i in by_size]
1761
1762 lock = threading.Lock()
1763 diff_iter = iter(by_size) # accessed under lock
1764
1765 def worker():
1766 try:
1767 lock.acquire()
1768 for d in diff_iter:
1769 lock.release()
1770 start = time.time()
1771 d.ComputePatch()
1772 dur = time.time() - start
1773 lock.acquire()
1774
1775 tf, sf, patch = d.GetPatch()
1776 if sf.name == tf.name:
1777 name = tf.name
1778 else:
1779 name = "%s (%s)" % (tf.name, sf.name)
1780 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001781 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001782 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001783 logger.info(
1784 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1785 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001786 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001787 except Exception:
1788 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001789 raise
1790
1791 # start worker threads; wait for them all to finish.
1792 threads = [threading.Thread(target=worker)
1793 for i in range(OPTIONS.worker_threads)]
1794 for th in threads:
1795 th.start()
1796 while threads:
1797 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001798
1799
Dan Albert8b72aef2015-03-23 19:13:21 -07001800class BlockDifference(object):
1801 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001802 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001803 self.tgt = tgt
1804 self.src = src
1805 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001806 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001807 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001808
Tao Baodd2a5892015-03-12 12:32:37 -07001809 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001810 version = max(
1811 int(i) for i in
1812 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001813 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001814 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001815
1816 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001817 version=self.version,
1818 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001819 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001820 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001821 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001822 self.touched_src_ranges = b.touched_src_ranges
1823 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001824
Yifan Hong10c530d2018-12-27 17:34:18 -08001825 # On devices with dynamic partitions, for new partitions,
1826 # src is None but OPTIONS.source_info_dict is not.
1827 if OPTIONS.source_info_dict is None:
1828 is_dynamic_build = OPTIONS.info_dict.get(
1829 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001830 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001831 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001832 is_dynamic_build = OPTIONS.source_info_dict.get(
1833 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001834 is_dynamic_source = partition in shlex.split(
1835 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001836
Yifan Hongbb2658d2019-01-25 12:30:58 -08001837 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001838 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1839
Yifan Hongbb2658d2019-01-25 12:30:58 -08001840 # For dynamic partitions builds, check partition list in both source
1841 # and target build because new partitions may be added, and existing
1842 # partitions may be removed.
1843 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1844
Yifan Hong10c530d2018-12-27 17:34:18 -08001845 if is_dynamic:
1846 self.device = 'map_partition("%s")' % partition
1847 else:
1848 if OPTIONS.source_info_dict is None:
1849 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1850 else:
1851 _, device_path = GetTypeAndDevice("/" + partition,
1852 OPTIONS.source_info_dict)
1853 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001854
Tao Baod8d14be2016-02-04 14:26:02 -08001855 @property
1856 def required_cache(self):
1857 return self._required_cache
1858
Tao Bao76def242017-11-21 09:25:31 -08001859 def WriteScript(self, script, output_zip, progress=None,
1860 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001861 if not self.src:
1862 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001863 script.Print("Patching %s image unconditionally..." % (self.partition,))
1864 else:
1865 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001866
Dan Albert8b72aef2015-03-23 19:13:21 -07001867 if progress:
1868 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001869 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001870
1871 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001872 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001873
Tao Bao9bc6bb22015-11-09 16:58:28 -08001874 def WriteStrictVerifyScript(self, script):
1875 """Verify all the blocks in the care_map, including clobbered blocks.
1876
1877 This differs from the WriteVerifyScript() function: a) it prints different
1878 error messages; b) it doesn't allow half-way updated images to pass the
1879 verification."""
1880
1881 partition = self.partition
1882 script.Print("Verifying %s..." % (partition,))
1883 ranges = self.tgt.care_map
1884 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001885 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001886 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1887 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001888 self.device, ranges_str,
1889 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001890 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001891 script.AppendExtra("")
1892
Tao Baod522bdc2016-04-12 15:53:16 -07001893 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001894 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001895
1896 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001897 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001898 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001899
1900 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001901 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001902 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001903 ranges = self.touched_src_ranges
1904 expected_sha1 = self.touched_src_sha1
1905 else:
1906 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1907 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001908
1909 # No blocks to be checked, skipping.
1910 if not ranges:
1911 return
1912
Tao Bao5ece99d2015-05-12 11:42:31 -07001913 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001914 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001915 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001916 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1917 '"%s.patch.dat")) then' % (
1918 self.device, ranges_str, expected_sha1,
1919 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001920 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001921 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001922
Tianjie Xufc3422a2015-12-15 11:53:59 -08001923 if self.version >= 4:
1924
1925 # Bug: 21124327
1926 # When generating incrementals for the system and vendor partitions in
1927 # version 4 or newer, explicitly check the first block (which contains
1928 # the superblock) of the partition to see if it's what we expect. If
1929 # this check fails, give an explicit log message about the partition
1930 # having been remounted R/W (the most likely explanation).
1931 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08001932 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08001933
1934 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001935 if partition == "system":
1936 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1937 else:
1938 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001939 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08001940 'ifelse (block_image_recover({device}, "{ranges}") && '
1941 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001942 'package_extract_file("{partition}.transfer.list"), '
1943 '"{partition}.new.dat", "{partition}.patch.dat"), '
1944 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001945 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001946 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001947 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001948
Tao Baodd2a5892015-03-12 12:32:37 -07001949 # Abort the OTA update. Note that the incremental OTA cannot be applied
1950 # even if it may match the checksum of the target partition.
1951 # a) If version < 3, operations like move and erase will make changes
1952 # unconditionally and damage the partition.
1953 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001954 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001955 if partition == "system":
1956 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1957 else:
1958 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1959 script.AppendExtra((
1960 'abort("E%d: %s partition has unexpected contents");\n'
1961 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001962
Yifan Hong10c530d2018-12-27 17:34:18 -08001963 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07001964 partition = self.partition
1965 script.Print('Verifying the updated %s image...' % (partition,))
1966 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1967 ranges = self.tgt.care_map
1968 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001969 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001970 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001971 self.device, ranges_str,
1972 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001973
1974 # Bug: 20881595
1975 # Verify that extended blocks are really zeroed out.
1976 if self.tgt.extended:
1977 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001978 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001979 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001980 self.device, ranges_str,
1981 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001982 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001983 if partition == "system":
1984 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1985 else:
1986 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001987 script.AppendExtra(
1988 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001989 ' abort("E%d: %s partition has unexpected non-zero contents after '
1990 'OTA update");\n'
1991 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001992 else:
1993 script.Print('Verified the updated %s image.' % (partition,))
1994
Tianjie Xu209db462016-05-24 17:34:52 -07001995 if partition == "system":
1996 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1997 else:
1998 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1999
Tao Bao5fcaaef2015-06-01 13:40:49 -07002000 script.AppendExtra(
2001 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002002 ' abort("E%d: %s partition has unexpected contents after OTA '
2003 'update");\n'
2004 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002005
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002006 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002007 ZipWrite(output_zip,
2008 '{}.transfer.list'.format(self.path),
2009 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002010
Tao Bao76def242017-11-21 09:25:31 -08002011 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2012 # its size. Quailty 9 almost triples the compression time but doesn't
2013 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002014 # zip | brotli(quality 6) | brotli(quality 9)
2015 # compressed_size: 942M | 869M (~8% reduced) | 854M
2016 # compression_time: 75s | 265s | 719s
2017 # decompression_time: 15s | 25s | 25s
2018
2019 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002020 brotli_cmd = ['brotli', '--quality=6',
2021 '--output={}.new.dat.br'.format(self.path),
2022 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002023 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002024 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002025
2026 new_data_name = '{}.new.dat.br'.format(self.partition)
2027 ZipWrite(output_zip,
2028 '{}.new.dat.br'.format(self.path),
2029 new_data_name,
2030 compress_type=zipfile.ZIP_STORED)
2031 else:
2032 new_data_name = '{}.new.dat'.format(self.partition)
2033 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2034
Dan Albert8e0178d2015-01-27 15:53:15 -08002035 ZipWrite(output_zip,
2036 '{}.patch.dat'.format(self.path),
2037 '{}.patch.dat'.format(self.partition),
2038 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002039
Tianjie Xu209db462016-05-24 17:34:52 -07002040 if self.partition == "system":
2041 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2042 else:
2043 code = ErrorCode.VENDOR_UPDATE_FAILURE
2044
Yifan Hong10c530d2018-12-27 17:34:18 -08002045 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002046 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002047 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002048 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002049 device=self.device, partition=self.partition,
2050 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002051 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002052
Dan Albert8b72aef2015-03-23 19:13:21 -07002053 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002054 data = source.ReadRangeSet(ranges)
2055 ctx = sha1()
2056
2057 for p in data:
2058 ctx.update(p)
2059
2060 return ctx.hexdigest()
2061
Tao Baoe9b61912015-07-09 17:37:49 -07002062 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2063 """Return the hash value for all zero blocks."""
2064 zero_block = '\x00' * 4096
2065 ctx = sha1()
2066 for _ in range(num_blocks):
2067 ctx.update(zero_block)
2068
2069 return ctx.hexdigest()
2070
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002071
2072DataImage = blockimgdiff.DataImage
2073
Tao Bao76def242017-11-21 09:25:31 -08002074
Doug Zongker96a57e72010-09-26 14:57:41 -07002075# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002076PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002077 "ext4": "EMMC",
2078 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002079 "f2fs": "EMMC",
2080 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002081}
Doug Zongker96a57e72010-09-26 14:57:41 -07002082
Tao Bao76def242017-11-21 09:25:31 -08002083
Doug Zongker96a57e72010-09-26 14:57:41 -07002084def GetTypeAndDevice(mount_point, info):
2085 fstab = info["fstab"]
2086 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002087 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2088 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002089 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002090 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002091
2092
2093def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002094 """Parses and converts a PEM-encoded certificate into DER-encoded.
2095
2096 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2097
2098 Returns:
2099 The decoded certificate string.
2100 """
2101 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002102 save = False
2103 for line in data.split("\n"):
2104 if "--END CERTIFICATE--" in line:
2105 break
2106 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002107 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002108 if "--BEGIN CERTIFICATE--" in line:
2109 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002110 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002111 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002112
Tao Bao04e1f012018-02-04 12:13:35 -08002113
2114def ExtractPublicKey(cert):
2115 """Extracts the public key (PEM-encoded) from the given certificate file.
2116
2117 Args:
2118 cert: The certificate filename.
2119
2120 Returns:
2121 The public key string.
2122
2123 Raises:
2124 AssertionError: On non-zero return from 'openssl'.
2125 """
2126 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2127 # While openssl 1.1 writes the key into the given filename followed by '-out',
2128 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2129 # stdout instead.
2130 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2131 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2132 pubkey, stderrdata = proc.communicate()
2133 assert proc.returncode == 0, \
2134 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2135 return pubkey
2136
2137
Tao Bao2cc0ca12019-03-15 10:44:43 -07002138def ExtractAvbPublicKey(key):
2139 """Extracts the AVB public key from the given public or private key.
2140
2141 Args:
2142 key: The input key file, which should be PEM-encoded public or private key.
2143
2144 Returns:
2145 The path to the extracted AVB public key file.
2146 """
2147 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2148 RunAndCheckOutput(
2149 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2150 return output
2151
2152
Doug Zongker412c02f2014-02-13 10:58:24 -08002153def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2154 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002155 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002156
Tao Bao6d5d6232018-03-09 17:04:42 -08002157 Most of the space in the boot and recovery images is just the kernel, which is
2158 identical for the two, so the resulting patch should be efficient. Add it to
2159 the output zip, along with a shell script that is run from init.rc on first
2160 boot to actually do the patching and install the new recovery image.
2161
2162 Args:
2163 input_dir: The top-level input directory of the target-files.zip.
2164 output_sink: The callback function that writes the result.
2165 recovery_img: File object for the recovery image.
2166 boot_img: File objects for the boot image.
2167 info_dict: A dict returned by common.LoadInfoDict() on the input
2168 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002169 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002170 if info_dict is None:
2171 info_dict = OPTIONS.info_dict
2172
Tao Bao6d5d6232018-03-09 17:04:42 -08002173 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002174
Tao Baof2cffbd2015-07-22 12:33:18 -07002175 if full_recovery_image:
2176 output_sink("etc/recovery.img", recovery_img.data)
2177
2178 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002179 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002180 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002181 # With system-root-image, boot and recovery images will have mismatching
2182 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2183 # to handle such a case.
2184 if system_root_image:
2185 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002186 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002187 assert not os.path.exists(path)
2188 else:
2189 diff_program = ["imgdiff"]
2190 if os.path.exists(path):
2191 diff_program.append("-b")
2192 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002193 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002194 else:
2195 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002196
2197 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2198 _, _, patch = d.ComputePatch()
2199 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002200
Dan Albertebb19aa2015-03-27 19:11:53 -07002201 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002202 # The following GetTypeAndDevice()s need to use the path in the target
2203 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002204 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2205 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2206 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002207 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002208
Tao Baof2cffbd2015-07-22 12:33:18 -07002209 if full_recovery_image:
2210 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002211if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2212 applypatch \\
2213 --flash /system/etc/recovery.img \\
2214 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2215 log -t recovery "Installing new recovery image: succeeded" || \\
2216 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002217else
2218 log -t recovery "Recovery image already installed"
2219fi
2220""" % {'type': recovery_type,
2221 'device': recovery_device,
2222 'sha1': recovery_img.sha1,
2223 'size': recovery_img.size}
2224 else:
2225 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002226if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2227 applypatch %(bonus_args)s \\
2228 --patch /system/recovery-from-boot.p \\
2229 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2230 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2231 log -t recovery "Installing new recovery image: succeeded" || \\
2232 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002233else
2234 log -t recovery "Recovery image already installed"
2235fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002236""" % {'boot_size': boot_img.size,
2237 'boot_sha1': boot_img.sha1,
2238 'recovery_size': recovery_img.size,
2239 'recovery_sha1': recovery_img.sha1,
2240 'boot_type': boot_type,
2241 'boot_device': boot_device,
2242 'recovery_type': recovery_type,
2243 'recovery_device': recovery_device,
2244 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002245
2246 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002247 # in the L release.
2248 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002249
Tao Bao32fcdab2018-10-12 10:30:39 -07002250 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002251
2252 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002253
2254
2255class DynamicPartitionUpdate(object):
2256 def __init__(self, src_group=None, tgt_group=None, progress=None,
2257 block_difference=None):
2258 self.src_group = src_group
2259 self.tgt_group = tgt_group
2260 self.progress = progress
2261 self.block_difference = block_difference
2262
2263 @property
2264 def src_size(self):
2265 if not self.block_difference:
2266 return 0
2267 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2268
2269 @property
2270 def tgt_size(self):
2271 if not self.block_difference:
2272 return 0
2273 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2274
2275 @staticmethod
2276 def _GetSparseImageSize(img):
2277 if not img:
2278 return 0
2279 return img.blocksize * img.total_blocks
2280
2281
2282class DynamicGroupUpdate(object):
2283 def __init__(self, src_size=None, tgt_size=None):
2284 # None: group does not exist. 0: no size limits.
2285 self.src_size = src_size
2286 self.tgt_size = tgt_size
2287
2288
2289class DynamicPartitionsDifference(object):
2290 def __init__(self, info_dict, block_diffs, progress_dict=None,
2291 source_info_dict=None):
2292 if progress_dict is None:
2293 progress_dict = dict()
2294
2295 self._remove_all_before_apply = False
2296 if source_info_dict is None:
2297 self._remove_all_before_apply = True
2298 source_info_dict = dict()
2299
2300 block_diff_dict = {e.partition:e for e in block_diffs}
2301 assert len(block_diff_dict) == len(block_diffs), \
2302 "Duplicated BlockDifference object for {}".format(
2303 [partition for partition, count in
2304 collections.Counter(e.partition for e in block_diffs).items()
2305 if count > 1])
2306
Yifan Hong79997e52019-01-23 16:56:19 -08002307 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002308
2309 for p, block_diff in block_diff_dict.items():
2310 self._partition_updates[p] = DynamicPartitionUpdate()
2311 self._partition_updates[p].block_difference = block_diff
2312
2313 for p, progress in progress_dict.items():
2314 if p in self._partition_updates:
2315 self._partition_updates[p].progress = progress
2316
2317 tgt_groups = shlex.split(info_dict.get(
2318 "super_partition_groups", "").strip())
2319 src_groups = shlex.split(source_info_dict.get(
2320 "super_partition_groups", "").strip())
2321
2322 for g in tgt_groups:
2323 for p in shlex.split(info_dict.get(
2324 "super_%s_partition_list" % g, "").strip()):
2325 assert p in self._partition_updates, \
2326 "{} is in target super_{}_partition_list but no BlockDifference " \
2327 "object is provided.".format(p, g)
2328 self._partition_updates[p].tgt_group = g
2329
2330 for g in src_groups:
2331 for p in shlex.split(source_info_dict.get(
2332 "super_%s_partition_list" % g, "").strip()):
2333 assert p in self._partition_updates, \
2334 "{} is in source super_{}_partition_list but no BlockDifference " \
2335 "object is provided.".format(p, g)
2336 self._partition_updates[p].src_group = g
2337
Yifan Hong45433e42019-01-18 13:55:25 -08002338 target_dynamic_partitions = set(shlex.split(info_dict.get(
2339 "dynamic_partition_list", "").strip()))
2340 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2341 if u.tgt_size)
2342 assert block_diffs_with_target == target_dynamic_partitions, \
2343 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2344 list(target_dynamic_partitions), list(block_diffs_with_target))
2345
2346 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2347 "dynamic_partition_list", "").strip()))
2348 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2349 if u.src_size)
2350 assert block_diffs_with_source == source_dynamic_partitions, \
2351 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2352 list(source_dynamic_partitions), list(block_diffs_with_source))
2353
Yifan Hong10c530d2018-12-27 17:34:18 -08002354 if self._partition_updates:
2355 logger.info("Updating dynamic partitions %s",
2356 self._partition_updates.keys())
2357
Yifan Hong79997e52019-01-23 16:56:19 -08002358 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002359
2360 for g in tgt_groups:
2361 self._group_updates[g] = DynamicGroupUpdate()
2362 self._group_updates[g].tgt_size = int(info_dict.get(
2363 "super_%s_group_size" % g, "0").strip())
2364
2365 for g in src_groups:
2366 if g not in self._group_updates:
2367 self._group_updates[g] = DynamicGroupUpdate()
2368 self._group_updates[g].src_size = int(source_info_dict.get(
2369 "super_%s_group_size" % g, "0").strip())
2370
2371 self._Compute()
2372
2373 def WriteScript(self, script, output_zip, write_verify_script=False):
2374 script.Comment('--- Start patching dynamic partitions ---')
2375 for p, u in self._partition_updates.items():
2376 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2377 script.Comment('Patch partition %s' % p)
2378 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2379 write_verify_script=False)
2380
2381 op_list_path = MakeTempFile()
2382 with open(op_list_path, 'w') as f:
2383 for line in self._op_list:
2384 f.write('{}\n'.format(line))
2385
2386 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2387
2388 script.Comment('Update dynamic partition metadata')
2389 script.AppendExtra('assert(update_dynamic_partitions('
2390 'package_extract_file("dynamic_partitions_op_list")));')
2391
2392 if write_verify_script:
2393 for p, u in self._partition_updates.items():
2394 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2395 u.block_difference.WritePostInstallVerifyScript(script)
2396 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2397
2398 for p, u in self._partition_updates.items():
2399 if u.tgt_size and u.src_size <= u.tgt_size:
2400 script.Comment('Patch partition %s' % p)
2401 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2402 write_verify_script=write_verify_script)
2403 if write_verify_script:
2404 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2405
2406 script.Comment('--- End patching dynamic partitions ---')
2407
2408 def _Compute(self):
2409 self._op_list = list()
2410
2411 def append(line):
2412 self._op_list.append(line)
2413
2414 def comment(line):
2415 self._op_list.append("# %s" % line)
2416
2417 if self._remove_all_before_apply:
2418 comment('Remove all existing dynamic partitions and groups before '
2419 'applying full OTA')
2420 append('remove_all_groups')
2421
2422 for p, u in self._partition_updates.items():
2423 if u.src_group and not u.tgt_group:
2424 append('remove %s' % p)
2425
2426 for p, u in self._partition_updates.items():
2427 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2428 comment('Move partition %s from %s to default' % (p, u.src_group))
2429 append('move %s default' % p)
2430
2431 for p, u in self._partition_updates.items():
2432 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2433 comment('Shrink partition %s from %d to %d' %
2434 (p, u.src_size, u.tgt_size))
2435 append('resize %s %s' % (p, u.tgt_size))
2436
2437 for g, u in self._group_updates.items():
2438 if u.src_size is not None and u.tgt_size is None:
2439 append('remove_group %s' % g)
2440 if (u.src_size is not None and u.tgt_size is not None and
2441 u.src_size > u.tgt_size):
2442 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2443 append('resize_group %s %d' % (g, u.tgt_size))
2444
2445 for g, u in self._group_updates.items():
2446 if u.src_size is None and u.tgt_size is not None:
2447 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2448 append('add_group %s %d' % (g, u.tgt_size))
2449 if (u.src_size is not None and u.tgt_size is not None and
2450 u.src_size < u.tgt_size):
2451 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2452 append('resize_group %s %d' % (g, u.tgt_size))
2453
2454 for p, u in self._partition_updates.items():
2455 if u.tgt_group and not u.src_group:
2456 comment('Add partition %s to group %s' % (p, u.tgt_group))
2457 append('add %s %s' % (p, u.tgt_group))
2458
2459 for p, u in self._partition_updates.items():
2460 if u.tgt_size and u.src_size < u.tgt_size:
2461 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2462 append('resize %s %d' % (p, u.tgt_size))
2463
2464 for p, u in self._partition_updates.items():
2465 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2466 comment('Move partition %s from default to %s' %
2467 (p, u.tgt_group))
2468 append('move %s %s' % (p, u.tgt_group))