blob: 8a60f7d7f6c4ae7dc5fc20e69ac6934541eceee1 [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
Doug Zongkereef39442009-04-02 12:14:19 -070020import getopt
21import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010022import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070023import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070024import json
25import logging
26import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070027import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080028import platform
Doug Zongkereef39442009-04-02 12:14:19 -070029import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070030import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070031import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080032import string
Doug Zongkereef39442009-04-02 12:14:19 -070033import subprocess
34import sys
35import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070036import threading
37import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070038import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080039from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070040
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070041import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080042import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070043
Tao Bao32fcdab2018-10-12 10:30:39 -070044logger = logging.getLogger(__name__)
45
Tao Bao986ee862018-10-04 15:46:16 -070046
Dan Albert8b72aef2015-03-23 19:13:21 -070047class Options(object):
48 def __init__(self):
49 platform_search_path = {
50 "linux2": "out/host/linux-x86",
51 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070052 }
Doug Zongker85448772014-09-09 14:59:20 -070053
Tao Bao76def242017-11-21 09:25:31 -080054 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070055 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080056 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.extra_signapk_args = []
58 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080059 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.public_key_suffix = ".x509.pem"
61 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070062 # use otatools built boot_signer by default
63 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070064 self.boot_signer_args = []
65 self.verity_signer_path = None
66 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.verbose = False
68 self.tempfiles = []
69 self.device_specific = None
70 self.extras = {}
71 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070072 self.source_info_dict = None
73 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070074 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070075 # Stash size cannot exceed cache_size * threshold.
76 self.cache_size = None
77 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070078
79
80OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070081
Tao Bao71197512018-10-11 14:08:45 -070082# The block size that's used across the releasetools scripts.
83BLOCK_SIZE = 4096
84
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080085# Values for "certificate" in apkcerts that mean special things.
86SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
87
Tao Bao9dd909e2017-11-14 11:27:32 -080088# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010089AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010090 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080091
Tianjie Xu861f4132018-09-12 11:49:33 -070092# Partitions that should have their care_map added to META/care_map.pb
93PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
94 'odm')
95
96
Tianjie Xu209db462016-05-24 17:34:52 -070097class ErrorCode(object):
98 """Define error_codes for failures that happen during the actual
99 update package installation.
100
101 Error codes 0-999 are reserved for failures before the package
102 installation (i.e. low battery, package verification failure).
103 Detailed code in 'bootable/recovery/error_code.h' """
104
105 SYSTEM_VERIFICATION_FAILURE = 1000
106 SYSTEM_UPDATE_FAILURE = 1001
107 SYSTEM_UNEXPECTED_CONTENTS = 1002
108 SYSTEM_NONZERO_CONTENTS = 1003
109 SYSTEM_RECOVER_FAILURE = 1004
110 VENDOR_VERIFICATION_FAILURE = 2000
111 VENDOR_UPDATE_FAILURE = 2001
112 VENDOR_UNEXPECTED_CONTENTS = 2002
113 VENDOR_NONZERO_CONTENTS = 2003
114 VENDOR_RECOVER_FAILURE = 2004
115 OEM_PROP_MISMATCH = 3000
116 FINGERPRINT_MISMATCH = 3001
117 THUMBPRINT_MISMATCH = 3002
118 OLDER_BUILD = 3003
119 DEVICE_MISMATCH = 3004
120 BAD_PATCH_FILE = 3005
121 INSUFFICIENT_CACHE_SPACE = 3006
122 TUNE_PARTITION_FAILURE = 3007
123 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800124
Tao Bao80921982018-03-21 21:02:19 -0700125
Dan Albert8b72aef2015-03-23 19:13:21 -0700126class ExternalError(RuntimeError):
127 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700128
129
Tao Bao32fcdab2018-10-12 10:30:39 -0700130def InitLogging():
131 DEFAULT_LOGGING_CONFIG = {
132 'version': 1,
133 'disable_existing_loggers': False,
134 'formatters': {
135 'standard': {
136 'format':
137 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
138 'datefmt': '%Y-%m-%d %H:%M:%S',
139 },
140 },
141 'handlers': {
142 'default': {
143 'class': 'logging.StreamHandler',
144 'formatter': 'standard',
145 },
146 },
147 'loggers': {
148 '': {
149 'handlers': ['default'],
150 'level': 'WARNING',
151 'propagate': True,
152 }
153 }
154 }
155 env_config = os.getenv('LOGGING_CONFIG')
156 if env_config:
157 with open(env_config) as f:
158 config = json.load(f)
159 else:
160 config = DEFAULT_LOGGING_CONFIG
161
162 # Increase the logging level for verbose mode.
163 if OPTIONS.verbose:
164 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
165 config['loggers']['']['level'] = 'INFO'
166
167 logging.config.dictConfig(config)
168
169
Tao Bao39451582017-05-04 11:10:47 -0700170def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700171 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700172
Tao Bao73dd4f42018-10-04 16:25:33 -0700173 Args:
174 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700175 verbose: Whether the commands should be shown. Default to the global
176 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700177 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
178 stdin, etc. stdout and stderr will default to subprocess.PIPE and
179 subprocess.STDOUT respectively unless caller specifies any of them.
180
181 Returns:
182 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700183 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700184 if 'stdout' not in kwargs and 'stderr' not in kwargs:
185 kwargs['stdout'] = subprocess.PIPE
186 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700187 # Don't log any if caller explicitly says so.
188 if verbose != False:
189 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700190 return subprocess.Popen(args, **kwargs)
191
192
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800193def RunAndWait(args, verbose=None, **kwargs):
194 """Runs the given command and returns the exit code.
195
196 Args:
197 args: The command represented as a list of strings.
198 verbose: Whether the commands should be shown. Default to the global
199 verbosity if unspecified.
200 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
201 stdin, etc. stdout and stderr will default to subprocess.PIPE and
202 subprocess.STDOUT respectively unless caller specifies any of them.
203
204 Returns:
205 The process return code.
206 """
207 proc = Run(args, verbose=verbose, **kwargs)
208 proc.wait()
209 return proc.returncode
210
211
Tao Bao986ee862018-10-04 15:46:16 -0700212def RunAndCheckOutput(args, verbose=None, **kwargs):
213 """Runs the given command and returns the output.
214
215 Args:
216 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700217 verbose: Whether the commands should be shown. Default to the global
218 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700219 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
220 stdin, etc. stdout and stderr will default to subprocess.PIPE and
221 subprocess.STDOUT respectively unless caller specifies any of them.
222
223 Returns:
224 The output string.
225
226 Raises:
227 ExternalError: On non-zero exit from the command.
228 """
Tao Bao986ee862018-10-04 15:46:16 -0700229 proc = Run(args, verbose=verbose, **kwargs)
230 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700231 # Don't log any if caller explicitly says so.
232 if verbose != False:
233 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700234 if proc.returncode != 0:
235 raise ExternalError(
236 "Failed to run command '{}' (exit code {}):\n{}".format(
237 args, proc.returncode, output))
238 return output
239
240
Tao Baoc765cca2018-01-31 17:32:40 -0800241def RoundUpTo4K(value):
242 rounded_up = value + 4095
243 return rounded_up - (rounded_up % 4096)
244
245
Ying Wang7e6d4e42010-12-13 16:25:36 -0800246def CloseInheritedPipes():
247 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
248 before doing other work."""
249 if platform.system() != "Darwin":
250 return
251 for d in range(3, 1025):
252 try:
253 stat = os.fstat(d)
254 if stat is not None:
255 pipebit = stat[0] & 0x1000
256 if pipebit != 0:
257 os.close(d)
258 except OSError:
259 pass
260
261
Tao Bao410ad8b2018-08-24 12:08:38 -0700262def LoadInfoDict(input_file, repacking=False):
263 """Loads the key/value pairs from the given input target_files.
264
265 It reads `META/misc_info.txt` file in the target_files input, does sanity
266 checks and returns the parsed key/value pairs for to the given build. It's
267 usually called early when working on input target_files files, e.g. when
268 generating OTAs, or signing builds. Note that the function may be called
269 against an old target_files file (i.e. from past dessert releases). So the
270 property parsing needs to be backward compatible.
271
272 In a `META/misc_info.txt`, a few properties are stored as links to the files
273 in the PRODUCT_OUT directory. It works fine with the build system. However,
274 they are no longer available when (re)generating images from target_files zip.
275 When `repacking` is True, redirect these properties to the actual files in the
276 unzipped directory.
277
278 Args:
279 input_file: The input target_files file, which could be an open
280 zipfile.ZipFile instance, or a str for the dir that contains the files
281 unzipped from a target_files file.
282 repacking: Whether it's trying repack an target_files file after loading the
283 info dict (default: False). If so, it will rewrite a few loaded
284 properties (e.g. selinux_fc, root_dir) to point to the actual files in
285 target_files file. When doing repacking, `input_file` must be a dir.
286
287 Returns:
288 A dict that contains the parsed key/value pairs.
289
290 Raises:
291 AssertionError: On invalid input arguments.
292 ValueError: On malformed input values.
293 """
294 if repacking:
295 assert isinstance(input_file, str), \
296 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700297
Doug Zongkerc9253822014-02-04 12:17:58 -0800298 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700299 if isinstance(input_file, zipfile.ZipFile):
300 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800301 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700302 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800303 try:
304 with open(path) as f:
305 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700306 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800307 if e.errno == errno.ENOENT:
308 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800309
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700310 try:
Michael Runge6e836112014-04-15 17:40:21 -0700311 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700312 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700313 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700314
Tao Bao410ad8b2018-08-24 12:08:38 -0700315 if "recovery_api_version" not in d:
316 raise ValueError("Failed to find 'recovery_api_version'")
317 if "fstab_version" not in d:
318 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800319
Tao Bao410ad8b2018-08-24 12:08:38 -0700320 if repacking:
321 # We carry a copy of file_contexts.bin under META/. If not available, search
322 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
323 # images than the one running on device, in that case, we must have the one
324 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700325 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700326 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700327 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700328
Tom Cherryd14b8952018-08-09 14:26:00 -0700329 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700330
Tom Cherryd14b8952018-08-09 14:26:00 -0700331 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700332 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700333 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700334 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700335
Tao Baof54216f2016-03-29 15:12:37 -0700336 # Redirect {system,vendor}_base_fs_file.
337 if "system_base_fs_file" in d:
338 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700339 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700340 if os.path.exists(system_base_fs_file):
341 d["system_base_fs_file"] = system_base_fs_file
342 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700343 logger.warning(
344 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700345 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700346
347 if "vendor_base_fs_file" in d:
348 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700349 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700350 if os.path.exists(vendor_base_fs_file):
351 d["vendor_base_fs_file"] = vendor_base_fs_file
352 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700353 logger.warning(
354 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700355 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700356
Doug Zongker37974732010-09-16 17:44:38 -0700357 def makeint(key):
358 if key in d:
359 d[key] = int(d[key], 0)
360
361 makeint("recovery_api_version")
362 makeint("blocksize")
363 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700364 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700365 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700366 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700367 makeint("recovery_size")
368 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800369 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700370
Tao Baoa57ab9f2018-08-24 12:08:38 -0700371 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
372 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
373 # cases, since it may load the info_dict from an old build (e.g. when
374 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800375 system_root_image = d.get("system_root_image") == "true"
376 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700377 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700378 if isinstance(input_file, zipfile.ZipFile):
379 if recovery_fstab_path not in input_file.namelist():
380 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
381 else:
382 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
383 if not os.path.exists(path):
384 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800385 d["fstab"] = LoadRecoveryFSTab(
386 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700387
Tao Bao76def242017-11-21 09:25:31 -0800388 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700389 recovery_fstab_path = "BOOT/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 = "BOOT/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 = "BOOT/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
Tianjie Xucfa86222016-03-07 16:31:19 -0800400 else:
401 d["fstab"] = None
402
Tianjie Xu861f4132018-09-12 11:49:33 -0700403 # Tries to load the build props for all partitions with care_map, including
404 # system and vendor.
405 for partition in PARTITIONS_WITH_CARE_MAP:
406 d["{}.build.prop".format(partition)] = LoadBuildProp(
407 read_helper, "{}/build.prop".format(partition.upper()))
408 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800409
410 # Set up the salt (based on fingerprint or thumbprint) that will be used when
411 # adding AVB footer.
412 if d.get("avb_enable") == "true":
413 fp = None
414 if "build.prop" in d:
415 build_prop = d["build.prop"]
416 if "ro.build.fingerprint" in build_prop:
417 fp = build_prop["ro.build.fingerprint"]
418 elif "ro.build.thumbprint" in build_prop:
419 fp = build_prop["ro.build.thumbprint"]
420 if fp:
421 d["avb_salt"] = sha256(fp).hexdigest()
422
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700423 return d
424
Tao Baod1de6f32017-03-01 16:38:48 -0800425
Tao Baobcd1d162017-08-26 13:10:26 -0700426def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700427 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700428 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700429 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700430 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700431 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700432 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700433
Tao Baod1de6f32017-03-01 16:38:48 -0800434
Michael Runge6e836112014-04-15 17:40:21 -0700435def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700436 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700437 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700438 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700439 if not line or line.startswith("#"):
440 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700441 if "=" in line:
442 name, value = line.split("=", 1)
443 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700444 return d
445
Tao Baod1de6f32017-03-01 16:38:48 -0800446
Tianjie Xucfa86222016-03-07 16:31:19 -0800447def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
448 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700449 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800450 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700451 self.mount_point = mount_point
452 self.fs_type = fs_type
453 self.device = device
454 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700455 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700456
457 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800458 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700459 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700460 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700461 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700462
Tao Baod1de6f32017-03-01 16:38:48 -0800463 assert fstab_version == 2
464
465 d = {}
466 for line in data.split("\n"):
467 line = line.strip()
468 if not line or line.startswith("#"):
469 continue
470
471 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
472 pieces = line.split()
473 if len(pieces) != 5:
474 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
475
476 # Ignore entries that are managed by vold.
477 options = pieces[4]
478 if "voldmanaged=" in options:
479 continue
480
481 # It's a good line, parse it.
482 length = 0
483 options = options.split(",")
484 for i in options:
485 if i.startswith("length="):
486 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800487 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800488 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700489 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800490
Tao Baod1de6f32017-03-01 16:38:48 -0800491 mount_flags = pieces[3]
492 # Honor the SELinux context if present.
493 context = None
494 for i in mount_flags.split(","):
495 if i.startswith("context="):
496 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800497
Tao Baod1de6f32017-03-01 16:38:48 -0800498 mount_point = pieces[1]
499 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
500 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800501
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700502 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700503 # system. Other areas assume system is always at "/system" so point /system
504 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700505 if system_root_image:
506 assert not d.has_key("/system") and d.has_key("/")
507 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700508 return d
509
510
Doug Zongker37974732010-09-16 17:44:38 -0700511def DumpInfoDict(d):
512 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700513 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700514
Dan Albert8b72aef2015-03-23 19:13:21 -0700515
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800516def AppendAVBSigningArgs(cmd, partition):
517 """Append signing arguments for avbtool."""
518 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
519 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
520 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
521 if key_path and algorithm:
522 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700523 avb_salt = OPTIONS.info_dict.get("avb_salt")
524 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700525 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700526 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800527
528
Tao Bao02a08592018-07-22 12:40:45 -0700529def GetAvbChainedPartitionArg(partition, info_dict, key=None):
530 """Constructs and returns the arg to build or verify a chained partition.
531
532 Args:
533 partition: The partition name.
534 info_dict: The info dict to look up the key info and rollback index
535 location.
536 key: The key to be used for building or verifying the partition. Defaults to
537 the key listed in info_dict.
538
539 Returns:
540 A string of form "partition:rollback_index_location:key" that can be used to
541 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700542 """
543 if key is None:
544 key = info_dict["avb_" + partition + "_key_path"]
545 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
546 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
Tao Bao986ee862018-10-04 15:46:16 -0700547 RunAndCheckOutput(
Tao Bao73dd4f42018-10-04 16:25:33 -0700548 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path])
Tao Bao02a08592018-07-22 12:40:45 -0700549
550 rollback_index_location = info_dict[
551 "avb_" + partition + "_rollback_index_location"]
552 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
553
554
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700555def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800556 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700557 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700558
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700559 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800560 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
561 we are building a two-step special image (i.e. building a recovery image to
562 be loaded into /boot in two-step OTAs).
563
564 Return the image data, or None if sourcedir does not appear to contains files
565 for building the requested image.
566 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700567
568 def make_ramdisk():
569 ramdisk_img = tempfile.NamedTemporaryFile()
570
571 if os.access(fs_config_file, os.F_OK):
572 cmd = ["mkbootfs", "-f", fs_config_file,
573 os.path.join(sourcedir, "RAMDISK")]
574 else:
575 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
576 p1 = Run(cmd, stdout=subprocess.PIPE)
577 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
578
579 p2.wait()
580 p1.wait()
581 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
582 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
583
584 return ramdisk_img
585
586 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
587 return None
588
589 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700590 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700591
Doug Zongkerd5131602012-08-02 14:46:42 -0700592 if info_dict is None:
593 info_dict = OPTIONS.info_dict
594
Doug Zongkereef39442009-04-02 12:14:19 -0700595 img = tempfile.NamedTemporaryFile()
596
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700597 if has_ramdisk:
598 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700599
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800600 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
601 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
602
603 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700604
Benoit Fradina45a8682014-07-14 21:00:43 +0200605 fn = os.path.join(sourcedir, "second")
606 if os.access(fn, os.F_OK):
607 cmd.append("--second")
608 cmd.append(fn)
609
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800610 fn = os.path.join(sourcedir, "dtb")
611 if os.access(fn, os.F_OK):
612 cmd.append("--dtb")
613 cmd.append(fn)
614
Doug Zongker171f1cd2009-06-15 22:36:37 -0700615 fn = os.path.join(sourcedir, "cmdline")
616 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700617 cmd.append("--cmdline")
618 cmd.append(open(fn).read().rstrip("\n"))
619
620 fn = os.path.join(sourcedir, "base")
621 if os.access(fn, os.F_OK):
622 cmd.append("--base")
623 cmd.append(open(fn).read().rstrip("\n"))
624
Ying Wang4de6b5b2010-08-25 14:29:34 -0700625 fn = os.path.join(sourcedir, "pagesize")
626 if os.access(fn, os.F_OK):
627 cmd.append("--pagesize")
628 cmd.append(open(fn).read().rstrip("\n"))
629
Tao Bao76def242017-11-21 09:25:31 -0800630 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700631 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700632 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700633
Tao Bao76def242017-11-21 09:25:31 -0800634 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000635 if args and args.strip():
636 cmd.extend(shlex.split(args))
637
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700638 if has_ramdisk:
639 cmd.extend(["--ramdisk", ramdisk_img.name])
640
Tao Baod95e9fd2015-03-29 23:07:41 -0700641 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800642 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700643 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700644 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700645 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700646 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700647
Tao Baobf70c312017-07-11 17:27:55 -0700648 # "boot" or "recovery", without extension.
649 partition_name = os.path.basename(sourcedir).lower()
650
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800651 if partition_name == "recovery":
652 if info_dict.get("include_recovery_dtbo") == "true":
653 fn = os.path.join(sourcedir, "recovery_dtbo")
654 cmd.extend(["--recovery_dtbo", fn])
655 if info_dict.get("include_recovery_acpio") == "true":
656 fn = os.path.join(sourcedir, "recovery_acpio")
657 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700658
Tao Bao986ee862018-10-04 15:46:16 -0700659 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700660
Tao Bao76def242017-11-21 09:25:31 -0800661 if (info_dict.get("boot_signer") == "true" and
662 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800663 # Hard-code the path as "/boot" for two-step special recovery image (which
664 # will be loaded into /boot during the two-step OTA).
665 if two_step_image:
666 path = "/boot"
667 else:
Tao Baobf70c312017-07-11 17:27:55 -0700668 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700669 cmd = [OPTIONS.boot_signer_path]
670 cmd.extend(OPTIONS.boot_signer_args)
671 cmd.extend([path, img.name,
672 info_dict["verity_key"] + ".pk8",
673 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700674 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700675
Tao Baod95e9fd2015-03-29 23:07:41 -0700676 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800677 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700678 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700679 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800680 # We have switched from the prebuilt futility binary to using the tool
681 # (futility-host) built from the source. Override the setting in the old
682 # TF.zip.
683 futility = info_dict["futility"]
684 if futility.startswith("prebuilts/"):
685 futility = "futility-host"
686 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700687 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700688 info_dict["vboot_key"] + ".vbprivk",
689 info_dict["vboot_subkey"] + ".vbprivk",
690 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700691 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700692 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700693
Tao Baof3282b42015-04-01 11:21:55 -0700694 # Clean up the temp files.
695 img_unsigned.close()
696 img_keyblock.close()
697
David Zeuthen8fecb282017-12-01 16:24:01 -0500698 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800699 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700700 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500701 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400702 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700703 "--partition_size", str(part_size), "--partition_name",
704 partition_name]
705 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500706 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400707 if args and args.strip():
708 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700709 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500710
711 img.seek(os.SEEK_SET, 0)
712 data = img.read()
713
714 if has_ramdisk:
715 ramdisk_img.close()
716 img.close()
717
718 return data
719
720
Doug Zongkerd5131602012-08-02 14:46:42 -0700721def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800722 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700723 """Return a File object with the desired bootable image.
724
725 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
726 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
727 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700728
Doug Zongker55d93282011-01-25 17:03:34 -0800729 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
730 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700731 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800732 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700733
734 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
735 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700736 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700737 return File.FromLocalFile(name, prebuilt_path)
738
Tao Bao32fcdab2018-10-12 10:30:39 -0700739 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700740
741 if info_dict is None:
742 info_dict = OPTIONS.info_dict
743
744 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800745 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
746 # for recovery.
747 has_ramdisk = (info_dict.get("system_root_image") != "true" or
748 prebuilt_name != "boot.img" or
749 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700750
Doug Zongker6f1d0312014-08-22 08:07:12 -0700751 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400752 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
753 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800754 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700755 if data:
756 return File(name, data)
757 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800758
Doug Zongkereef39442009-04-02 12:14:19 -0700759
Narayan Kamatha07bf042017-08-14 14:49:21 +0100760def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800761 """Gunzips the given gzip compressed file to a given output file."""
762 with gzip.open(in_filename, "rb") as in_file, \
763 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100764 shutil.copyfileobj(in_file, out_file)
765
766
Doug Zongker75f17362009-12-08 13:46:44 -0800767def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800768 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800769
Tao Bao1c830bf2017-12-25 10:43:47 -0800770 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
771 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800772
Tao Bao1c830bf2017-12-25 10:43:47 -0800773 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800774 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800775 """
Doug Zongkereef39442009-04-02 12:14:19 -0700776
Doug Zongker55d93282011-01-25 17:03:34 -0800777 def unzip_to_dir(filename, dirname):
778 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
779 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800780 cmd.extend(pattern)
Tao Bao986ee862018-10-04 15:46:16 -0700781 RunAndCheckOutput(cmd)
Doug Zongker55d93282011-01-25 17:03:34 -0800782
Tao Bao1c830bf2017-12-25 10:43:47 -0800783 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800784 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
785 if m:
786 unzip_to_dir(m.group(1), tmp)
787 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
788 filename = m.group(1)
789 else:
790 unzip_to_dir(filename, tmp)
791
Tao Baodba59ee2018-01-09 13:21:02 -0800792 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700793
794
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700795def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
796 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800797 """Returns a SparseImage object suitable for passing to BlockImageDiff.
798
799 This function loads the specified sparse image from the given path, and
800 performs additional processing for OTA purpose. For example, it always adds
801 block 0 to clobbered blocks list. It also detects files that cannot be
802 reconstructed from the block list, for whom we should avoid applying imgdiff.
803
804 Args:
805 which: The partition name, which must be "system" or "vendor".
806 tmpdir: The directory that contains the prebuilt image and block map file.
807 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800808 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700809 hashtree_info_generator: If present, generates the hashtree_info for this
810 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800811 Returns:
812 A SparseImage object, with file_map info loaded.
813 """
814 assert which in ("system", "vendor")
815
816 path = os.path.join(tmpdir, "IMAGES", which + ".img")
817 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
818
819 # The image and map files must have been created prior to calling
820 # ota_from_target_files.py (since LMP).
821 assert os.path.exists(path) and os.path.exists(mappath)
822
823 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
824 # it to clobbered_blocks so that it will be written to the target
825 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
826 clobbered_blocks = "0"
827
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700828 image = sparse_img.SparseImage(
829 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
830 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800831
832 # block.map may contain less blocks, because mke2fs may skip allocating blocks
833 # if they contain all zeros. We can't reconstruct such a file from its block
834 # list. Tag such entries accordingly. (Bug: 65213616)
835 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800836 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700837 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800838 continue
839
Tom Cherryd14b8952018-08-09 14:26:00 -0700840 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
841 # filename listed in system.map may contain an additional leading slash
842 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
843 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700844 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
845
Tom Cherryd14b8952018-08-09 14:26:00 -0700846 # Special handling another case, where files not under /system
847 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700848 if which == 'system' and not arcname.startswith('SYSTEM'):
849 arcname = 'ROOT/' + arcname
850
851 assert arcname in input_zip.namelist(), \
852 "Failed to find the ZIP entry for {}".format(entry)
853
Tao Baoc765cca2018-01-31 17:32:40 -0800854 info = input_zip.getinfo(arcname)
855 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800856
857 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800858 # image, check the original block list to determine its completeness. Note
859 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800860 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800861 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800862
Tao Baoc765cca2018-01-31 17:32:40 -0800863 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
864 ranges.extra['incomplete'] = True
865
866 return image
867
868
Doug Zongkereef39442009-04-02 12:14:19 -0700869def GetKeyPasswords(keylist):
870 """Given a list of keys, prompt the user to enter passwords for
871 those which require them. Return a {key: password} dict. password
872 will be None if the key has no password."""
873
Doug Zongker8ce7c252009-05-22 13:34:54 -0700874 no_passwords = []
875 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700876 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700877 devnull = open("/dev/null", "w+b")
878 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800879 # We don't need a password for things that aren't really keys.
880 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700881 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700882 continue
883
T.R. Fullhart37e10522013-03-18 10:31:26 -0700884 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700885 "-inform", "DER", "-nocrypt"],
886 stdin=devnull.fileno(),
887 stdout=devnull.fileno(),
888 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700889 p.communicate()
890 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700891 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700892 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700893 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700894 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
895 "-inform", "DER", "-passin", "pass:"],
896 stdin=devnull.fileno(),
897 stdout=devnull.fileno(),
898 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700899 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700900 if p.returncode == 0:
901 # Encrypted key with empty string as password.
902 key_passwords[k] = ''
903 elif stderr.startswith('Error decrypting key'):
904 # Definitely encrypted key.
905 # It would have said "Error reading key" if it didn't parse correctly.
906 need_passwords.append(k)
907 else:
908 # Potentially, a type of key that openssl doesn't understand.
909 # We'll let the routines in signapk.jar handle it.
910 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700911 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700912
T.R. Fullhart37e10522013-03-18 10:31:26 -0700913 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800914 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700915 return key_passwords
916
917
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800918def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700919 """Gets the minSdkVersion declared in the APK.
920
921 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
922 This can be both a decimal number (API Level) or a codename.
923
924 Args:
925 apk_name: The APK filename.
926
927 Returns:
928 The parsed SDK version string.
929
930 Raises:
931 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800932 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700933 proc = Run(
934 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
935 stderr=subprocess.PIPE)
936 stdoutdata, stderrdata = proc.communicate()
937 if proc.returncode != 0:
938 raise ExternalError(
939 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
940 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800941
Tao Baof47bf0f2018-03-21 23:28:51 -0700942 for line in stdoutdata.split("\n"):
943 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800944 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
945 if m:
946 return m.group(1)
947 raise ExternalError("No minSdkVersion returned by aapt")
948
949
950def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700951 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800952
Tao Baof47bf0f2018-03-21 23:28:51 -0700953 If minSdkVersion is set to a codename, it is translated to a number using the
954 provided map.
955
956 Args:
957 apk_name: The APK filename.
958
959 Returns:
960 The parsed SDK version number.
961
962 Raises:
963 ExternalError: On failing to get the min SDK version number.
964 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800965 version = GetMinSdkVersion(apk_name)
966 try:
967 return int(version)
968 except ValueError:
969 # Not a decimal number. Codename?
970 if version in codename_to_api_level_map:
971 return codename_to_api_level_map[version]
972 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700973 raise ExternalError(
974 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
975 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800976
977
978def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800979 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700980 """Sign the input_name zip/jar/apk, producing output_name. Use the
981 given key and password (the latter may be None if the key does not
982 have a password.
983
Doug Zongker951495f2009-08-14 12:44:19 -0700984 If whole_file is true, use the "-w" option to SignApk to embed a
985 signature that covers the whole file in the archive comment of the
986 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800987
988 min_api_level is the API Level (int) of the oldest platform this file may end
989 up on. If not specified for an APK, the API Level is obtained by interpreting
990 the minSdkVersion attribute of the APK's AndroidManifest.xml.
991
992 codename_to_api_level_map is needed to translate the codename which may be
993 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700994 """
Tao Bao76def242017-11-21 09:25:31 -0800995 if codename_to_api_level_map is None:
996 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700997
Alex Klyubin9667b182015-12-10 13:38:50 -0800998 java_library_path = os.path.join(
999 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1000
Tao Baoe95540e2016-11-08 12:08:53 -08001001 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1002 ["-Djava.library.path=" + java_library_path,
1003 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
1004 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001005 if whole_file:
1006 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001007
1008 min_sdk_version = min_api_level
1009 if min_sdk_version is None:
1010 if not whole_file:
1011 min_sdk_version = GetMinSdkVersionInt(
1012 input_name, codename_to_api_level_map)
1013 if min_sdk_version is not None:
1014 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1015
T.R. Fullhart37e10522013-03-18 10:31:26 -07001016 cmd.extend([key + OPTIONS.public_key_suffix,
1017 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001018 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001019
Tao Bao73dd4f42018-10-04 16:25:33 -07001020 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001021 if password is not None:
1022 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001023 stdoutdata, _ = proc.communicate(password)
1024 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001025 raise ExternalError(
1026 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001027 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001028
Doug Zongkereef39442009-04-02 12:14:19 -07001029
Doug Zongker37974732010-09-16 17:44:38 -07001030def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001031 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001032
Tao Bao9dd909e2017-11-14 11:27:32 -08001033 For non-AVB images, raise exception if the data is too big. Print a warning
1034 if the data is nearing the maximum size.
1035
1036 For AVB images, the actual image size should be identical to the limit.
1037
1038 Args:
1039 data: A string that contains all the data for the partition.
1040 target: The partition name. The ".img" suffix is optional.
1041 info_dict: The dict to be looked up for relevant info.
1042 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001043 if target.endswith(".img"):
1044 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001045 mount_point = "/" + target
1046
Ying Wangf8824af2014-06-03 14:07:27 -07001047 fs_type = None
1048 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001049 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001050 if mount_point == "/userdata":
1051 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001052 p = info_dict["fstab"][mount_point]
1053 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001054 device = p.device
1055 if "/" in device:
1056 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001057 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001058 if not fs_type or not limit:
1059 return
Doug Zongkereef39442009-04-02 12:14:19 -07001060
Andrew Boie0f9aec82012-02-14 09:32:52 -08001061 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001062 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1063 # path.
1064 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1065 if size != limit:
1066 raise ExternalError(
1067 "Mismatching image size for %s: expected %d actual %d" % (
1068 target, limit, size))
1069 else:
1070 pct = float(size) * 100.0 / limit
1071 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1072 if pct >= 99.0:
1073 raise ExternalError(msg)
1074 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001075 logger.warning("\n WARNING: %s\n", msg)
1076 else:
1077 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001078
1079
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001080def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001081 """Parses the APK certs info from a given target-files zip.
1082
1083 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1084 tuple with the following elements: (1) a dictionary that maps packages to
1085 certs (based on the "certificate" and "private_key" attributes in the file;
1086 (2) a string representing the extension of compressed APKs in the target files
1087 (e.g ".gz", ".bro").
1088
1089 Args:
1090 tf_zip: The input target_files ZipFile (already open).
1091
1092 Returns:
1093 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1094 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1095 no compressed APKs.
1096 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001097 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001098 compressed_extension = None
1099
Tao Bao0f990332017-09-08 19:02:54 -07001100 # META/apkcerts.txt contains the info for _all_ the packages known at build
1101 # time. Filter out the ones that are not installed.
1102 installed_files = set()
1103 for name in tf_zip.namelist():
1104 basename = os.path.basename(name)
1105 if basename:
1106 installed_files.add(basename)
1107
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001108 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1109 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001110 if not line:
1111 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001112 m = re.match(
1113 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1114 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1115 line)
1116 if not m:
1117 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001118
Tao Bao818ddf52018-01-05 11:17:34 -08001119 matches = m.groupdict()
1120 cert = matches["CERT"]
1121 privkey = matches["PRIVKEY"]
1122 name = matches["NAME"]
1123 this_compressed_extension = matches["COMPRESSED"]
1124
1125 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1126 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1127 if cert in SPECIAL_CERT_STRINGS and not privkey:
1128 certmap[name] = cert
1129 elif (cert.endswith(OPTIONS.public_key_suffix) and
1130 privkey.endswith(OPTIONS.private_key_suffix) and
1131 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1132 certmap[name] = cert[:-public_key_suffix_len]
1133 else:
1134 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1135
1136 if not this_compressed_extension:
1137 continue
1138
1139 # Only count the installed files.
1140 filename = name + '.' + this_compressed_extension
1141 if filename not in installed_files:
1142 continue
1143
1144 # Make sure that all the values in the compression map have the same
1145 # extension. We don't support multiple compression methods in the same
1146 # system image.
1147 if compressed_extension:
1148 if this_compressed_extension != compressed_extension:
1149 raise ValueError(
1150 "Multiple compressed extensions: {} vs {}".format(
1151 compressed_extension, this_compressed_extension))
1152 else:
1153 compressed_extension = this_compressed_extension
1154
1155 return (certmap,
1156 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001157
1158
Doug Zongkereef39442009-04-02 12:14:19 -07001159COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001160Global options
1161
1162 -p (--path) <dir>
1163 Prepend <dir>/bin to the list of places to search for binaries run by this
1164 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001165
Doug Zongker05d3dea2009-06-22 11:32:31 -07001166 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001167 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001168
Tao Bao30df8b42018-04-23 15:32:53 -07001169 -x (--extra) <key=value>
1170 Add a key/value pair to the 'extras' dict, which device-specific extension
1171 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001172
Doug Zongkereef39442009-04-02 12:14:19 -07001173 -v (--verbose)
1174 Show command lines being executed.
1175
1176 -h (--help)
1177 Display this usage message and exit.
1178"""
1179
1180def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001181 print(docstring.rstrip("\n"))
1182 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001183
1184
1185def ParseOptions(argv,
1186 docstring,
1187 extra_opts="", extra_long_opts=(),
1188 extra_option_handler=None):
1189 """Parse the options in argv and return any arguments that aren't
1190 flags. docstring is the calling module's docstring, to be displayed
1191 for errors and -h. extra_opts and extra_long_opts are for flags
1192 defined by the caller, which are processed by passing them to
1193 extra_option_handler."""
1194
1195 try:
1196 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001197 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001198 ["help", "verbose", "path=", "signapk_path=",
1199 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001200 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001201 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1202 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001203 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001204 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001205 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001206 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001207 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001208 sys.exit(2)
1209
Doug Zongkereef39442009-04-02 12:14:19 -07001210 for o, a in opts:
1211 if o in ("-h", "--help"):
1212 Usage(docstring)
1213 sys.exit()
1214 elif o in ("-v", "--verbose"):
1215 OPTIONS.verbose = True
1216 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001217 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001218 elif o in ("--signapk_path",):
1219 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001220 elif o in ("--signapk_shared_library_path",):
1221 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001222 elif o in ("--extra_signapk_args",):
1223 OPTIONS.extra_signapk_args = shlex.split(a)
1224 elif o in ("--java_path",):
1225 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001226 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001227 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001228 elif o in ("--public_key_suffix",):
1229 OPTIONS.public_key_suffix = a
1230 elif o in ("--private_key_suffix",):
1231 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001232 elif o in ("--boot_signer_path",):
1233 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001234 elif o in ("--boot_signer_args",):
1235 OPTIONS.boot_signer_args = shlex.split(a)
1236 elif o in ("--verity_signer_path",):
1237 OPTIONS.verity_signer_path = a
1238 elif o in ("--verity_signer_args",):
1239 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001240 elif o in ("-s", "--device_specific"):
1241 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001242 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001243 key, value = a.split("=", 1)
1244 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001245 else:
1246 if extra_option_handler is None or not extra_option_handler(o, a):
1247 assert False, "unknown option \"%s\"" % (o,)
1248
Doug Zongker85448772014-09-09 14:59:20 -07001249 if OPTIONS.search_path:
1250 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1251 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001252
1253 return args
1254
1255
Tao Bao4c851b12016-09-19 13:54:38 -07001256def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001257 """Make a temp file and add it to the list of things to be deleted
1258 when Cleanup() is called. Return the filename."""
1259 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1260 os.close(fd)
1261 OPTIONS.tempfiles.append(fn)
1262 return fn
1263
1264
Tao Bao1c830bf2017-12-25 10:43:47 -08001265def MakeTempDir(prefix='tmp', suffix=''):
1266 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1267
1268 Returns:
1269 The absolute pathname of the new directory.
1270 """
1271 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1272 OPTIONS.tempfiles.append(dir_name)
1273 return dir_name
1274
1275
Doug Zongkereef39442009-04-02 12:14:19 -07001276def Cleanup():
1277 for i in OPTIONS.tempfiles:
1278 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001279 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001280 else:
1281 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001282 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001283
1284
1285class PasswordManager(object):
1286 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001287 self.editor = os.getenv("EDITOR")
1288 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001289
1290 def GetPasswords(self, items):
1291 """Get passwords corresponding to each string in 'items',
1292 returning a dict. (The dict may have keys in addition to the
1293 values in 'items'.)
1294
1295 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1296 user edit that file to add more needed passwords. If no editor is
1297 available, or $ANDROID_PW_FILE isn't define, prompts the user
1298 interactively in the ordinary way.
1299 """
1300
1301 current = self.ReadFile()
1302
1303 first = True
1304 while True:
1305 missing = []
1306 for i in items:
1307 if i not in current or not current[i]:
1308 missing.append(i)
1309 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001310 if not missing:
1311 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001312
1313 for i in missing:
1314 current[i] = ""
1315
1316 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001317 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001318 answer = raw_input("try to edit again? [y]> ").strip()
1319 if answer and answer[0] not in 'yY':
1320 raise RuntimeError("key passwords unavailable")
1321 first = False
1322
1323 current = self.UpdateAndReadFile(current)
1324
Dan Albert8b72aef2015-03-23 19:13:21 -07001325 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001326 """Prompt the user to enter a value (password) for each key in
1327 'current' whose value is fales. Returns a new dict with all the
1328 values.
1329 """
1330 result = {}
1331 for k, v in sorted(current.iteritems()):
1332 if v:
1333 result[k] = v
1334 else:
1335 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001336 result[k] = getpass.getpass(
1337 "Enter password for %s key> " % k).strip()
1338 if result[k]:
1339 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001340 return result
1341
1342 def UpdateAndReadFile(self, current):
1343 if not self.editor or not self.pwfile:
1344 return self.PromptResult(current)
1345
1346 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001347 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001348 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1349 f.write("# (Additional spaces are harmless.)\n\n")
1350
1351 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001352 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1353 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001354 f.write("[[[ %s ]]] %s\n" % (v, k))
1355 if not v and first_line is None:
1356 # position cursor on first line with no password.
1357 first_line = i + 4
1358 f.close()
1359
Tao Bao986ee862018-10-04 15:46:16 -07001360 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001361
1362 return self.ReadFile()
1363
1364 def ReadFile(self):
1365 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001366 if self.pwfile is None:
1367 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001368 try:
1369 f = open(self.pwfile, "r")
1370 for line in f:
1371 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001372 if not line or line[0] == '#':
1373 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001374 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1375 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001376 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001377 else:
1378 result[m.group(2)] = m.group(1)
1379 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001380 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001381 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001382 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001383 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001384
1385
Dan Albert8e0178d2015-01-27 15:53:15 -08001386def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1387 compress_type=None):
1388 import datetime
1389
1390 # http://b/18015246
1391 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1392 # for files larger than 2GiB. We can work around this by adjusting their
1393 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1394 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1395 # it isn't clear to me exactly what circumstances cause this).
1396 # `zipfile.write()` must be used directly to work around this.
1397 #
1398 # This mess can be avoided if we port to python3.
1399 saved_zip64_limit = zipfile.ZIP64_LIMIT
1400 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1401
1402 if compress_type is None:
1403 compress_type = zip_file.compression
1404 if arcname is None:
1405 arcname = filename
1406
1407 saved_stat = os.stat(filename)
1408
1409 try:
1410 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1411 # file to be zipped and reset it when we're done.
1412 os.chmod(filename, perms)
1413
1414 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001415 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1416 # intentional. zip stores datetimes in local time without a time zone
1417 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1418 # in the zip archive.
1419 local_epoch = datetime.datetime.fromtimestamp(0)
1420 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001421 os.utime(filename, (timestamp, timestamp))
1422
1423 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1424 finally:
1425 os.chmod(filename, saved_stat.st_mode)
1426 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1427 zipfile.ZIP64_LIMIT = saved_zip64_limit
1428
1429
Tao Bao58c1b962015-05-20 09:32:18 -07001430def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001431 compress_type=None):
1432 """Wrap zipfile.writestr() function to work around the zip64 limit.
1433
1434 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1435 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1436 when calling crc32(bytes).
1437
1438 But it still works fine to write a shorter string into a large zip file.
1439 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1440 when we know the string won't be too long.
1441 """
1442
1443 saved_zip64_limit = zipfile.ZIP64_LIMIT
1444 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1445
1446 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1447 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001448 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001449 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001450 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001451 else:
Tao Baof3282b42015-04-01 11:21:55 -07001452 zinfo = zinfo_or_arcname
1453
1454 # If compress_type is given, it overrides the value in zinfo.
1455 if compress_type is not None:
1456 zinfo.compress_type = compress_type
1457
Tao Bao58c1b962015-05-20 09:32:18 -07001458 # If perms is given, it has a priority.
1459 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001460 # If perms doesn't set the file type, mark it as a regular file.
1461 if perms & 0o770000 == 0:
1462 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001463 zinfo.external_attr = perms << 16
1464
Tao Baof3282b42015-04-01 11:21:55 -07001465 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001466 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1467
Dan Albert8b72aef2015-03-23 19:13:21 -07001468 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001469 zipfile.ZIP64_LIMIT = saved_zip64_limit
1470
1471
Tao Bao89d7ab22017-12-14 17:05:33 -08001472def ZipDelete(zip_filename, entries):
1473 """Deletes entries from a ZIP file.
1474
1475 Since deleting entries from a ZIP file is not supported, it shells out to
1476 'zip -d'.
1477
1478 Args:
1479 zip_filename: The name of the ZIP file.
1480 entries: The name of the entry, or the list of names to be deleted.
1481
1482 Raises:
1483 AssertionError: In case of non-zero return from 'zip'.
1484 """
1485 if isinstance(entries, basestring):
1486 entries = [entries]
1487 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001488 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001489
1490
Tao Baof3282b42015-04-01 11:21:55 -07001491def ZipClose(zip_file):
1492 # http://b/18015246
1493 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1494 # central directory.
1495 saved_zip64_limit = zipfile.ZIP64_LIMIT
1496 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1497
1498 zip_file.close()
1499
1500 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001501
1502
1503class DeviceSpecificParams(object):
1504 module = None
1505 def __init__(self, **kwargs):
1506 """Keyword arguments to the constructor become attributes of this
1507 object, which is passed to all functions in the device-specific
1508 module."""
1509 for k, v in kwargs.iteritems():
1510 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001511 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001512
1513 if self.module is None:
1514 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001515 if not path:
1516 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001517 try:
1518 if os.path.isdir(path):
1519 info = imp.find_module("releasetools", [path])
1520 else:
1521 d, f = os.path.split(path)
1522 b, x = os.path.splitext(f)
1523 if x == ".py":
1524 f = b
1525 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001526 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001527 self.module = imp.load_module("device_specific", *info)
1528 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001529 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001530
1531 def _DoCall(self, function_name, *args, **kwargs):
1532 """Call the named function in the device-specific module, passing
1533 the given args and kwargs. The first argument to the call will be
1534 the DeviceSpecific object itself. If there is no module, or the
1535 module does not define the function, return the value of the
1536 'default' kwarg (which itself defaults to None)."""
1537 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001538 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001539 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1540
1541 def FullOTA_Assertions(self):
1542 """Called after emitting the block of assertions at the top of a
1543 full OTA package. Implementations can add whatever additional
1544 assertions they like."""
1545 return self._DoCall("FullOTA_Assertions")
1546
Doug Zongkere5ff5902012-01-17 10:55:37 -08001547 def FullOTA_InstallBegin(self):
1548 """Called at the start of full OTA installation."""
1549 return self._DoCall("FullOTA_InstallBegin")
1550
Yifan Hong10c530d2018-12-27 17:34:18 -08001551 def FullOTA_GetBlockDifferences(self):
1552 """Called during full OTA installation and verification.
1553 Implementation should return a list of BlockDifference objects describing
1554 the update on each additional partitions.
1555 """
1556 return self._DoCall("FullOTA_GetBlockDifferences")
1557
Doug Zongker05d3dea2009-06-22 11:32:31 -07001558 def FullOTA_InstallEnd(self):
1559 """Called at the end of full OTA installation; typically this is
1560 used to install the image for the device's baseband processor."""
1561 return self._DoCall("FullOTA_InstallEnd")
1562
1563 def IncrementalOTA_Assertions(self):
1564 """Called after emitting the block of assertions at the top of an
1565 incremental OTA package. Implementations can add whatever
1566 additional assertions they like."""
1567 return self._DoCall("IncrementalOTA_Assertions")
1568
Doug Zongkere5ff5902012-01-17 10:55:37 -08001569 def IncrementalOTA_VerifyBegin(self):
1570 """Called at the start of the verification phase of incremental
1571 OTA installation; additional checks can be placed here to abort
1572 the script before any changes are made."""
1573 return self._DoCall("IncrementalOTA_VerifyBegin")
1574
Doug Zongker05d3dea2009-06-22 11:32:31 -07001575 def IncrementalOTA_VerifyEnd(self):
1576 """Called at the end of the verification phase of incremental OTA
1577 installation; additional checks can be placed here to abort the
1578 script before any changes are made."""
1579 return self._DoCall("IncrementalOTA_VerifyEnd")
1580
Doug Zongkere5ff5902012-01-17 10:55:37 -08001581 def IncrementalOTA_InstallBegin(self):
1582 """Called at the start of incremental OTA installation (after
1583 verification is complete)."""
1584 return self._DoCall("IncrementalOTA_InstallBegin")
1585
Yifan Hong10c530d2018-12-27 17:34:18 -08001586 def IncrementalOTA_GetBlockDifferences(self):
1587 """Called during incremental OTA installation and verification.
1588 Implementation should return a list of BlockDifference objects describing
1589 the update on each additional partitions.
1590 """
1591 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1592
Doug Zongker05d3dea2009-06-22 11:32:31 -07001593 def IncrementalOTA_InstallEnd(self):
1594 """Called at the end of incremental OTA installation; typically
1595 this is used to install the image for the device's baseband
1596 processor."""
1597 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001598
Tao Bao9bc6bb22015-11-09 16:58:28 -08001599 def VerifyOTA_Assertions(self):
1600 return self._DoCall("VerifyOTA_Assertions")
1601
Tao Bao76def242017-11-21 09:25:31 -08001602
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001603class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001604 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001605 self.name = name
1606 self.data = data
1607 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001608 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001609 self.sha1 = sha1(data).hexdigest()
1610
1611 @classmethod
1612 def FromLocalFile(cls, name, diskname):
1613 f = open(diskname, "rb")
1614 data = f.read()
1615 f.close()
1616 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001617
1618 def WriteToTemp(self):
1619 t = tempfile.NamedTemporaryFile()
1620 t.write(self.data)
1621 t.flush()
1622 return t
1623
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001624 def WriteToDir(self, d):
1625 with open(os.path.join(d, self.name), "wb") as fp:
1626 fp.write(self.data)
1627
Geremy Condra36bd3652014-02-06 19:45:10 -08001628 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001629 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001630
Tao Bao76def242017-11-21 09:25:31 -08001631
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001632DIFF_PROGRAM_BY_EXT = {
1633 ".gz" : "imgdiff",
1634 ".zip" : ["imgdiff", "-z"],
1635 ".jar" : ["imgdiff", "-z"],
1636 ".apk" : ["imgdiff", "-z"],
1637 ".img" : "imgdiff",
1638 }
1639
Tao Bao76def242017-11-21 09:25:31 -08001640
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001641class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001642 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001643 self.tf = tf
1644 self.sf = sf
1645 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001646 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001647
1648 def ComputePatch(self):
1649 """Compute the patch (as a string of data) needed to turn sf into
1650 tf. Returns the same tuple as GetPatch()."""
1651
1652 tf = self.tf
1653 sf = self.sf
1654
Doug Zongker24cd2802012-08-14 16:36:15 -07001655 if self.diff_program:
1656 diff_program = self.diff_program
1657 else:
1658 ext = os.path.splitext(tf.name)[1]
1659 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001660
1661 ttemp = tf.WriteToTemp()
1662 stemp = sf.WriteToTemp()
1663
1664 ext = os.path.splitext(tf.name)[1]
1665
1666 try:
1667 ptemp = tempfile.NamedTemporaryFile()
1668 if isinstance(diff_program, list):
1669 cmd = copy.copy(diff_program)
1670 else:
1671 cmd = [diff_program]
1672 cmd.append(stemp.name)
1673 cmd.append(ttemp.name)
1674 cmd.append(ptemp.name)
1675 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001676 err = []
1677 def run():
1678 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001679 if e:
1680 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001681 th = threading.Thread(target=run)
1682 th.start()
1683 th.join(timeout=300) # 5 mins
1684 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001685 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001686 p.terminate()
1687 th.join(5)
1688 if th.is_alive():
1689 p.kill()
1690 th.join()
1691
Tianjie Xua2a9f992018-01-05 15:15:54 -08001692 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001693 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001694 self.patch = None
1695 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001696 diff = ptemp.read()
1697 finally:
1698 ptemp.close()
1699 stemp.close()
1700 ttemp.close()
1701
1702 self.patch = diff
1703 return self.tf, self.sf, self.patch
1704
1705
1706 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001707 """Returns a tuple of (target_file, source_file, patch_data).
1708
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001709 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001710 computing the patch failed.
1711 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001712 return self.tf, self.sf, self.patch
1713
1714
1715def ComputeDifferences(diffs):
1716 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001717 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001718
1719 # Do the largest files first, to try and reduce the long-pole effect.
1720 by_size = [(i.tf.size, i) for i in diffs]
1721 by_size.sort(reverse=True)
1722 by_size = [i[1] for i in by_size]
1723
1724 lock = threading.Lock()
1725 diff_iter = iter(by_size) # accessed under lock
1726
1727 def worker():
1728 try:
1729 lock.acquire()
1730 for d in diff_iter:
1731 lock.release()
1732 start = time.time()
1733 d.ComputePatch()
1734 dur = time.time() - start
1735 lock.acquire()
1736
1737 tf, sf, patch = d.GetPatch()
1738 if sf.name == tf.name:
1739 name = tf.name
1740 else:
1741 name = "%s (%s)" % (tf.name, sf.name)
1742 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001743 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001744 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001745 logger.info(
1746 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1747 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001748 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001749 except Exception:
1750 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001751 raise
1752
1753 # start worker threads; wait for them all to finish.
1754 threads = [threading.Thread(target=worker)
1755 for i in range(OPTIONS.worker_threads)]
1756 for th in threads:
1757 th.start()
1758 while threads:
1759 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001760
1761
Dan Albert8b72aef2015-03-23 19:13:21 -07001762class BlockDifference(object):
1763 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001764 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001765 self.tgt = tgt
1766 self.src = src
1767 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001768 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001769 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001770
Tao Baodd2a5892015-03-12 12:32:37 -07001771 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001772 version = max(
1773 int(i) for i in
1774 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001775 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001776 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001777
1778 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001779 version=self.version,
1780 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001781 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001782 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001783 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001784 self.touched_src_ranges = b.touched_src_ranges
1785 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001786
Yifan Hong10c530d2018-12-27 17:34:18 -08001787 # On devices with dynamic partitions, for new partitions,
1788 # src is None but OPTIONS.source_info_dict is not.
1789 if OPTIONS.source_info_dict is None:
1790 is_dynamic_build = OPTIONS.info_dict.get(
1791 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001792 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001793 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001794 is_dynamic_build = OPTIONS.source_info_dict.get(
1795 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001796 is_dynamic_source = partition in shlex.split(
1797 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001798
Yifan Hongbb2658d2019-01-25 12:30:58 -08001799 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001800 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1801
Yifan Hongbb2658d2019-01-25 12:30:58 -08001802 # For dynamic partitions builds, check partition list in both source
1803 # and target build because new partitions may be added, and existing
1804 # partitions may be removed.
1805 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1806
Yifan Hong10c530d2018-12-27 17:34:18 -08001807 if is_dynamic:
1808 self.device = 'map_partition("%s")' % partition
1809 else:
1810 if OPTIONS.source_info_dict is None:
1811 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1812 else:
1813 _, device_path = GetTypeAndDevice("/" + partition,
1814 OPTIONS.source_info_dict)
1815 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001816
Tao Baod8d14be2016-02-04 14:26:02 -08001817 @property
1818 def required_cache(self):
1819 return self._required_cache
1820
Tao Bao76def242017-11-21 09:25:31 -08001821 def WriteScript(self, script, output_zip, progress=None,
1822 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001823 if not self.src:
1824 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001825 script.Print("Patching %s image unconditionally..." % (self.partition,))
1826 else:
1827 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001828
Dan Albert8b72aef2015-03-23 19:13:21 -07001829 if progress:
1830 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001831 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001832
1833 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001834 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001835
Tao Bao9bc6bb22015-11-09 16:58:28 -08001836 def WriteStrictVerifyScript(self, script):
1837 """Verify all the blocks in the care_map, including clobbered blocks.
1838
1839 This differs from the WriteVerifyScript() function: a) it prints different
1840 error messages; b) it doesn't allow half-way updated images to pass the
1841 verification."""
1842
1843 partition = self.partition
1844 script.Print("Verifying %s..." % (partition,))
1845 ranges = self.tgt.care_map
1846 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001847 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001848 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1849 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001850 self.device, ranges_str,
1851 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001852 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001853 script.AppendExtra("")
1854
Tao Baod522bdc2016-04-12 15:53:16 -07001855 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001856 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001857
1858 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001859 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001860 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001861
1862 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001863 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001864 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001865 ranges = self.touched_src_ranges
1866 expected_sha1 = self.touched_src_sha1
1867 else:
1868 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1869 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001870
1871 # No blocks to be checked, skipping.
1872 if not ranges:
1873 return
1874
Tao Bao5ece99d2015-05-12 11:42:31 -07001875 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001876 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001877 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001878 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1879 '"%s.patch.dat")) then' % (
1880 self.device, ranges_str, expected_sha1,
1881 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001882 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001883 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001884
Tianjie Xufc3422a2015-12-15 11:53:59 -08001885 if self.version >= 4:
1886
1887 # Bug: 21124327
1888 # When generating incrementals for the system and vendor partitions in
1889 # version 4 or newer, explicitly check the first block (which contains
1890 # the superblock) of the partition to see if it's what we expect. If
1891 # this check fails, give an explicit log message about the partition
1892 # having been remounted R/W (the most likely explanation).
1893 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08001894 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08001895
1896 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001897 if partition == "system":
1898 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1899 else:
1900 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001901 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08001902 'ifelse (block_image_recover({device}, "{ranges}") && '
1903 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001904 'package_extract_file("{partition}.transfer.list"), '
1905 '"{partition}.new.dat", "{partition}.patch.dat"), '
1906 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001907 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001908 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001909 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001910
Tao Baodd2a5892015-03-12 12:32:37 -07001911 # Abort the OTA update. Note that the incremental OTA cannot be applied
1912 # even if it may match the checksum of the target partition.
1913 # a) If version < 3, operations like move and erase will make changes
1914 # unconditionally and damage the partition.
1915 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001916 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001917 if partition == "system":
1918 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1919 else:
1920 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1921 script.AppendExtra((
1922 'abort("E%d: %s partition has unexpected contents");\n'
1923 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001924
Yifan Hong10c530d2018-12-27 17:34:18 -08001925 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07001926 partition = self.partition
1927 script.Print('Verifying the updated %s image...' % (partition,))
1928 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1929 ranges = self.tgt.care_map
1930 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001931 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001932 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001933 self.device, ranges_str,
1934 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001935
1936 # Bug: 20881595
1937 # Verify that extended blocks are really zeroed out.
1938 if self.tgt.extended:
1939 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001940 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001941 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001942 self.device, ranges_str,
1943 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001944 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001945 if partition == "system":
1946 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1947 else:
1948 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001949 script.AppendExtra(
1950 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001951 ' abort("E%d: %s partition has unexpected non-zero contents after '
1952 'OTA update");\n'
1953 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001954 else:
1955 script.Print('Verified the updated %s image.' % (partition,))
1956
Tianjie Xu209db462016-05-24 17:34:52 -07001957 if partition == "system":
1958 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1959 else:
1960 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1961
Tao Bao5fcaaef2015-06-01 13:40:49 -07001962 script.AppendExtra(
1963 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001964 ' abort("E%d: %s partition has unexpected contents after OTA '
1965 'update");\n'
1966 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001967
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001968 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001969 ZipWrite(output_zip,
1970 '{}.transfer.list'.format(self.path),
1971 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001972
Tao Bao76def242017-11-21 09:25:31 -08001973 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1974 # its size. Quailty 9 almost triples the compression time but doesn't
1975 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001976 # zip | brotli(quality 6) | brotli(quality 9)
1977 # compressed_size: 942M | 869M (~8% reduced) | 854M
1978 # compression_time: 75s | 265s | 719s
1979 # decompression_time: 15s | 25s | 25s
1980
1981 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001982 brotli_cmd = ['brotli', '--quality=6',
1983 '--output={}.new.dat.br'.format(self.path),
1984 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001985 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07001986 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001987
1988 new_data_name = '{}.new.dat.br'.format(self.partition)
1989 ZipWrite(output_zip,
1990 '{}.new.dat.br'.format(self.path),
1991 new_data_name,
1992 compress_type=zipfile.ZIP_STORED)
1993 else:
1994 new_data_name = '{}.new.dat'.format(self.partition)
1995 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1996
Dan Albert8e0178d2015-01-27 15:53:15 -08001997 ZipWrite(output_zip,
1998 '{}.patch.dat'.format(self.path),
1999 '{}.patch.dat'.format(self.partition),
2000 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002001
Tianjie Xu209db462016-05-24 17:34:52 -07002002 if self.partition == "system":
2003 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2004 else:
2005 code = ErrorCode.VENDOR_UPDATE_FAILURE
2006
Yifan Hong10c530d2018-12-27 17:34:18 -08002007 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002008 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002009 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002010 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002011 device=self.device, partition=self.partition,
2012 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002013 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002014
Dan Albert8b72aef2015-03-23 19:13:21 -07002015 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002016 data = source.ReadRangeSet(ranges)
2017 ctx = sha1()
2018
2019 for p in data:
2020 ctx.update(p)
2021
2022 return ctx.hexdigest()
2023
Tao Baoe9b61912015-07-09 17:37:49 -07002024 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2025 """Return the hash value for all zero blocks."""
2026 zero_block = '\x00' * 4096
2027 ctx = sha1()
2028 for _ in range(num_blocks):
2029 ctx.update(zero_block)
2030
2031 return ctx.hexdigest()
2032
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002033
2034DataImage = blockimgdiff.DataImage
2035
Tao Bao76def242017-11-21 09:25:31 -08002036
Doug Zongker96a57e72010-09-26 14:57:41 -07002037# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002038PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002039 "ext4": "EMMC",
2040 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002041 "f2fs": "EMMC",
2042 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002043}
Doug Zongker96a57e72010-09-26 14:57:41 -07002044
Tao Bao76def242017-11-21 09:25:31 -08002045
Doug Zongker96a57e72010-09-26 14:57:41 -07002046def GetTypeAndDevice(mount_point, info):
2047 fstab = info["fstab"]
2048 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002049 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2050 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002051 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002052 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002053
2054
2055def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002056 """Parses and converts a PEM-encoded certificate into DER-encoded.
2057
2058 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2059
2060 Returns:
2061 The decoded certificate string.
2062 """
2063 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002064 save = False
2065 for line in data.split("\n"):
2066 if "--END CERTIFICATE--" in line:
2067 break
2068 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002069 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002070 if "--BEGIN CERTIFICATE--" in line:
2071 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002072 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002073 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002074
Tao Bao04e1f012018-02-04 12:13:35 -08002075
2076def ExtractPublicKey(cert):
2077 """Extracts the public key (PEM-encoded) from the given certificate file.
2078
2079 Args:
2080 cert: The certificate filename.
2081
2082 Returns:
2083 The public key string.
2084
2085 Raises:
2086 AssertionError: On non-zero return from 'openssl'.
2087 """
2088 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2089 # While openssl 1.1 writes the key into the given filename followed by '-out',
2090 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2091 # stdout instead.
2092 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2093 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2094 pubkey, stderrdata = proc.communicate()
2095 assert proc.returncode == 0, \
2096 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2097 return pubkey
2098
2099
Doug Zongker412c02f2014-02-13 10:58:24 -08002100def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2101 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002102 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002103
Tao Bao6d5d6232018-03-09 17:04:42 -08002104 Most of the space in the boot and recovery images is just the kernel, which is
2105 identical for the two, so the resulting patch should be efficient. Add it to
2106 the output zip, along with a shell script that is run from init.rc on first
2107 boot to actually do the patching and install the new recovery image.
2108
2109 Args:
2110 input_dir: The top-level input directory of the target-files.zip.
2111 output_sink: The callback function that writes the result.
2112 recovery_img: File object for the recovery image.
2113 boot_img: File objects for the boot image.
2114 info_dict: A dict returned by common.LoadInfoDict() on the input
2115 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002116 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002117 if info_dict is None:
2118 info_dict = OPTIONS.info_dict
2119
Tao Bao6d5d6232018-03-09 17:04:42 -08002120 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002121
Tao Baof2cffbd2015-07-22 12:33:18 -07002122 if full_recovery_image:
2123 output_sink("etc/recovery.img", recovery_img.data)
2124
2125 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002126 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002127 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002128 # With system-root-image, boot and recovery images will have mismatching
2129 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2130 # to handle such a case.
2131 if system_root_image:
2132 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002133 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002134 assert not os.path.exists(path)
2135 else:
2136 diff_program = ["imgdiff"]
2137 if os.path.exists(path):
2138 diff_program.append("-b")
2139 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002140 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002141 else:
2142 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002143
2144 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2145 _, _, patch = d.ComputePatch()
2146 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002147
Dan Albertebb19aa2015-03-27 19:11:53 -07002148 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002149 # The following GetTypeAndDevice()s need to use the path in the target
2150 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002151 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2152 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2153 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002154 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002155
Tao Baof2cffbd2015-07-22 12:33:18 -07002156 if full_recovery_image:
2157 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002158if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2159 applypatch \\
2160 --flash /system/etc/recovery.img \\
2161 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2162 log -t recovery "Installing new recovery image: succeeded" || \\
2163 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002164else
2165 log -t recovery "Recovery image already installed"
2166fi
2167""" % {'type': recovery_type,
2168 'device': recovery_device,
2169 'sha1': recovery_img.sha1,
2170 'size': recovery_img.size}
2171 else:
2172 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002173if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2174 applypatch %(bonus_args)s \\
2175 --patch /system/recovery-from-boot.p \\
2176 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2177 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2178 log -t recovery "Installing new recovery image: succeeded" || \\
2179 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002180else
2181 log -t recovery "Recovery image already installed"
2182fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002183""" % {'boot_size': boot_img.size,
2184 'boot_sha1': boot_img.sha1,
2185 'recovery_size': recovery_img.size,
2186 'recovery_sha1': recovery_img.sha1,
2187 'boot_type': boot_type,
2188 'boot_device': boot_device,
2189 'recovery_type': recovery_type,
2190 'recovery_device': recovery_device,
2191 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002192
2193 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002194 # in the L release.
2195 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002196
Tao Bao32fcdab2018-10-12 10:30:39 -07002197 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002198
2199 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002200
2201
2202class DynamicPartitionUpdate(object):
2203 def __init__(self, src_group=None, tgt_group=None, progress=None,
2204 block_difference=None):
2205 self.src_group = src_group
2206 self.tgt_group = tgt_group
2207 self.progress = progress
2208 self.block_difference = block_difference
2209
2210 @property
2211 def src_size(self):
2212 if not self.block_difference:
2213 return 0
2214 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2215
2216 @property
2217 def tgt_size(self):
2218 if not self.block_difference:
2219 return 0
2220 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2221
2222 @staticmethod
2223 def _GetSparseImageSize(img):
2224 if not img:
2225 return 0
2226 return img.blocksize * img.total_blocks
2227
2228
2229class DynamicGroupUpdate(object):
2230 def __init__(self, src_size=None, tgt_size=None):
2231 # None: group does not exist. 0: no size limits.
2232 self.src_size = src_size
2233 self.tgt_size = tgt_size
2234
2235
2236class DynamicPartitionsDifference(object):
2237 def __init__(self, info_dict, block_diffs, progress_dict=None,
2238 source_info_dict=None):
2239 if progress_dict is None:
2240 progress_dict = dict()
2241
2242 self._remove_all_before_apply = False
2243 if source_info_dict is None:
2244 self._remove_all_before_apply = True
2245 source_info_dict = dict()
2246
2247 block_diff_dict = {e.partition:e for e in block_diffs}
2248 assert len(block_diff_dict) == len(block_diffs), \
2249 "Duplicated BlockDifference object for {}".format(
2250 [partition for partition, count in
2251 collections.Counter(e.partition for e in block_diffs).items()
2252 if count > 1])
2253
Yifan Hong79997e52019-01-23 16:56:19 -08002254 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002255
2256 for p, block_diff in block_diff_dict.items():
2257 self._partition_updates[p] = DynamicPartitionUpdate()
2258 self._partition_updates[p].block_difference = block_diff
2259
2260 for p, progress in progress_dict.items():
2261 if p in self._partition_updates:
2262 self._partition_updates[p].progress = progress
2263
2264 tgt_groups = shlex.split(info_dict.get(
2265 "super_partition_groups", "").strip())
2266 src_groups = shlex.split(source_info_dict.get(
2267 "super_partition_groups", "").strip())
2268
2269 for g in tgt_groups:
2270 for p in shlex.split(info_dict.get(
2271 "super_%s_partition_list" % g, "").strip()):
2272 assert p in self._partition_updates, \
2273 "{} is in target super_{}_partition_list but no BlockDifference " \
2274 "object is provided.".format(p, g)
2275 self._partition_updates[p].tgt_group = g
2276
2277 for g in src_groups:
2278 for p in shlex.split(source_info_dict.get(
2279 "super_%s_partition_list" % g, "").strip()):
2280 assert p in self._partition_updates, \
2281 "{} is in source super_{}_partition_list but no BlockDifference " \
2282 "object is provided.".format(p, g)
2283 self._partition_updates[p].src_group = g
2284
Yifan Hong45433e42019-01-18 13:55:25 -08002285 target_dynamic_partitions = set(shlex.split(info_dict.get(
2286 "dynamic_partition_list", "").strip()))
2287 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2288 if u.tgt_size)
2289 assert block_diffs_with_target == target_dynamic_partitions, \
2290 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2291 list(target_dynamic_partitions), list(block_diffs_with_target))
2292
2293 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2294 "dynamic_partition_list", "").strip()))
2295 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2296 if u.src_size)
2297 assert block_diffs_with_source == source_dynamic_partitions, \
2298 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2299 list(source_dynamic_partitions), list(block_diffs_with_source))
2300
Yifan Hong10c530d2018-12-27 17:34:18 -08002301 if self._partition_updates:
2302 logger.info("Updating dynamic partitions %s",
2303 self._partition_updates.keys())
2304
Yifan Hong79997e52019-01-23 16:56:19 -08002305 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002306
2307 for g in tgt_groups:
2308 self._group_updates[g] = DynamicGroupUpdate()
2309 self._group_updates[g].tgt_size = int(info_dict.get(
2310 "super_%s_group_size" % g, "0").strip())
2311
2312 for g in src_groups:
2313 if g not in self._group_updates:
2314 self._group_updates[g] = DynamicGroupUpdate()
2315 self._group_updates[g].src_size = int(source_info_dict.get(
2316 "super_%s_group_size" % g, "0").strip())
2317
2318 self._Compute()
2319
2320 def WriteScript(self, script, output_zip, write_verify_script=False):
2321 script.Comment('--- Start patching dynamic partitions ---')
2322 for p, u in self._partition_updates.items():
2323 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2324 script.Comment('Patch partition %s' % p)
2325 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2326 write_verify_script=False)
2327
2328 op_list_path = MakeTempFile()
2329 with open(op_list_path, 'w') as f:
2330 for line in self._op_list:
2331 f.write('{}\n'.format(line))
2332
2333 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2334
2335 script.Comment('Update dynamic partition metadata')
2336 script.AppendExtra('assert(update_dynamic_partitions('
2337 'package_extract_file("dynamic_partitions_op_list")));')
2338
2339 if write_verify_script:
2340 for p, u in self._partition_updates.items():
2341 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2342 u.block_difference.WritePostInstallVerifyScript(script)
2343 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2344
2345 for p, u in self._partition_updates.items():
2346 if u.tgt_size and u.src_size <= u.tgt_size:
2347 script.Comment('Patch partition %s' % p)
2348 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2349 write_verify_script=write_verify_script)
2350 if write_verify_script:
2351 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2352
2353 script.Comment('--- End patching dynamic partitions ---')
2354
2355 def _Compute(self):
2356 self._op_list = list()
2357
2358 def append(line):
2359 self._op_list.append(line)
2360
2361 def comment(line):
2362 self._op_list.append("# %s" % line)
2363
2364 if self._remove_all_before_apply:
2365 comment('Remove all existing dynamic partitions and groups before '
2366 'applying full OTA')
2367 append('remove_all_groups')
2368
2369 for p, u in self._partition_updates.items():
2370 if u.src_group and not u.tgt_group:
2371 append('remove %s' % p)
2372
2373 for p, u in self._partition_updates.items():
2374 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2375 comment('Move partition %s from %s to default' % (p, u.src_group))
2376 append('move %s default' % p)
2377
2378 for p, u in self._partition_updates.items():
2379 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2380 comment('Shrink partition %s from %d to %d' %
2381 (p, u.src_size, u.tgt_size))
2382 append('resize %s %s' % (p, u.tgt_size))
2383
2384 for g, u in self._group_updates.items():
2385 if u.src_size is not None and u.tgt_size is None:
2386 append('remove_group %s' % g)
2387 if (u.src_size is not None and u.tgt_size is not None and
2388 u.src_size > u.tgt_size):
2389 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2390 append('resize_group %s %d' % (g, u.tgt_size))
2391
2392 for g, u in self._group_updates.items():
2393 if u.src_size is None and u.tgt_size is not None:
2394 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2395 append('add_group %s %d' % (g, u.tgt_size))
2396 if (u.src_size is not None and u.tgt_size is not None and
2397 u.src_size < u.tgt_size):
2398 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2399 append('resize_group %s %d' % (g, u.tgt_size))
2400
2401 for p, u in self._partition_updates.items():
2402 if u.tgt_group and not u.src_group:
2403 comment('Add partition %s to group %s' % (p, u.tgt_group))
2404 append('add %s %s' % (p, u.tgt_group))
2405
2406 for p, u in self._partition_updates.items():
2407 if u.tgt_size and u.src_size < u.tgt_size:
2408 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2409 append('resize %s %d' % (p, u.tgt_size))
2410
2411 for p, u in self._partition_updates.items():
2412 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2413 comment('Move partition %s from default to %s' %
2414 (p, u.tgt_group))
2415 append('move %s %s' % (p, u.tgt_group))