blob: 780b9c1550e5ab4ee1a5ce657ff9a40baa501e77 [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):
Pavel Salomatov32676552019-03-06 20:00:45 +030049 base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
50 if base_out_path is None:
51 base_search_path = "out"
52 else:
Tao Bao2cc0ca12019-03-15 10:44:43 -070053 base_search_path = os.path.join(base_out_path,
54 os.path.basename(os.getcwd()))
Pavel Salomatov32676552019-03-06 20:00:45 +030055
Dan Albert8b72aef2015-03-23 19:13:21 -070056 platform_search_path = {
Pavel Salomatov32676552019-03-06 20:00:45 +030057 "linux2": os.path.join(base_search_path, "host/linux-x86"),
58 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070059 }
Doug Zongker85448772014-09-09 14:59:20 -070060
Tao Bao76def242017-11-21 09:25:31 -080061 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070062 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080063 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070064 self.extra_signapk_args = []
65 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080066 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.public_key_suffix = ".x509.pem"
68 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070069 # use otatools built boot_signer by default
70 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070071 self.boot_signer_args = []
72 self.verity_signer_path = None
73 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070074 self.verbose = False
75 self.tempfiles = []
76 self.device_specific = None
77 self.extras = {}
78 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070079 self.source_info_dict = None
80 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070081 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070082 # Stash size cannot exceed cache_size * threshold.
83 self.cache_size = None
84 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070085
86
87OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070088
Tao Bao71197512018-10-11 14:08:45 -070089# The block size that's used across the releasetools scripts.
90BLOCK_SIZE = 4096
91
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080092# Values for "certificate" in apkcerts that mean special things.
93SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
94
Tao Bao9dd909e2017-11-14 11:27:32 -080095# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010096AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010097 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080098
Tianjie Xu861f4132018-09-12 11:49:33 -070099# Partitions that should have their care_map added to META/care_map.pb
100PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
101 'odm')
102
103
Tianjie Xu209db462016-05-24 17:34:52 -0700104class ErrorCode(object):
105 """Define error_codes for failures that happen during the actual
106 update package installation.
107
108 Error codes 0-999 are reserved for failures before the package
109 installation (i.e. low battery, package verification failure).
110 Detailed code in 'bootable/recovery/error_code.h' """
111
112 SYSTEM_VERIFICATION_FAILURE = 1000
113 SYSTEM_UPDATE_FAILURE = 1001
114 SYSTEM_UNEXPECTED_CONTENTS = 1002
115 SYSTEM_NONZERO_CONTENTS = 1003
116 SYSTEM_RECOVER_FAILURE = 1004
117 VENDOR_VERIFICATION_FAILURE = 2000
118 VENDOR_UPDATE_FAILURE = 2001
119 VENDOR_UNEXPECTED_CONTENTS = 2002
120 VENDOR_NONZERO_CONTENTS = 2003
121 VENDOR_RECOVER_FAILURE = 2004
122 OEM_PROP_MISMATCH = 3000
123 FINGERPRINT_MISMATCH = 3001
124 THUMBPRINT_MISMATCH = 3002
125 OLDER_BUILD = 3003
126 DEVICE_MISMATCH = 3004
127 BAD_PATCH_FILE = 3005
128 INSUFFICIENT_CACHE_SPACE = 3006
129 TUNE_PARTITION_FAILURE = 3007
130 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800131
Tao Bao80921982018-03-21 21:02:19 -0700132
Dan Albert8b72aef2015-03-23 19:13:21 -0700133class ExternalError(RuntimeError):
134 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700135
136
Tao Bao32fcdab2018-10-12 10:30:39 -0700137def InitLogging():
138 DEFAULT_LOGGING_CONFIG = {
139 'version': 1,
140 'disable_existing_loggers': False,
141 'formatters': {
142 'standard': {
143 'format':
144 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
145 'datefmt': '%Y-%m-%d %H:%M:%S',
146 },
147 },
148 'handlers': {
149 'default': {
150 'class': 'logging.StreamHandler',
151 'formatter': 'standard',
152 },
153 },
154 'loggers': {
155 '': {
156 'handlers': ['default'],
157 'level': 'WARNING',
158 'propagate': True,
159 }
160 }
161 }
162 env_config = os.getenv('LOGGING_CONFIG')
163 if env_config:
164 with open(env_config) as f:
165 config = json.load(f)
166 else:
167 config = DEFAULT_LOGGING_CONFIG
168
169 # Increase the logging level for verbose mode.
170 if OPTIONS.verbose:
171 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
172 config['loggers']['']['level'] = 'INFO'
173
174 logging.config.dictConfig(config)
175
176
Tao Bao39451582017-05-04 11:10:47 -0700177def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700178 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700179
Tao Bao73dd4f42018-10-04 16:25:33 -0700180 Args:
181 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700182 verbose: Whether the commands should be shown. Default to the global
183 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700184 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
185 stdin, etc. stdout and stderr will default to subprocess.PIPE and
186 subprocess.STDOUT respectively unless caller specifies any of them.
187
188 Returns:
189 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700190 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700191 if 'stdout' not in kwargs and 'stderr' not in kwargs:
192 kwargs['stdout'] = subprocess.PIPE
193 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700194 # Don't log any if caller explicitly says so.
195 if verbose != False:
196 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700197 return subprocess.Popen(args, **kwargs)
198
199
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800200def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800201 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800202
203 Args:
204 args: The command represented as a list of strings.
205 verbose: Whether the commands should be shown. Default to the global
206 verbosity if unspecified.
207 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
208 stdin, etc. stdout and stderr will default to subprocess.PIPE and
209 subprocess.STDOUT respectively unless caller specifies any of them.
210
Bill Peckham889b0c62019-02-21 18:53:37 -0800211 Raises:
212 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800213 """
214 proc = Run(args, verbose=verbose, **kwargs)
215 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800216
217 if proc.returncode != 0:
218 raise ExternalError(
219 "Failed to run command '{}' (exit code {})".format(
220 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800221
222
Tao Bao986ee862018-10-04 15:46:16 -0700223def RunAndCheckOutput(args, verbose=None, **kwargs):
224 """Runs the given command and returns the output.
225
226 Args:
227 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700228 verbose: Whether the commands should be shown. Default to the global
229 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700230 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
231 stdin, etc. stdout and stderr will default to subprocess.PIPE and
232 subprocess.STDOUT respectively unless caller specifies any of them.
233
234 Returns:
235 The output string.
236
237 Raises:
238 ExternalError: On non-zero exit from the command.
239 """
Tao Bao986ee862018-10-04 15:46:16 -0700240 proc = Run(args, verbose=verbose, **kwargs)
241 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700242 # Don't log any if caller explicitly says so.
243 if verbose != False:
244 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700245 if proc.returncode != 0:
246 raise ExternalError(
247 "Failed to run command '{}' (exit code {}):\n{}".format(
248 args, proc.returncode, output))
249 return output
250
251
Tao Baoc765cca2018-01-31 17:32:40 -0800252def RoundUpTo4K(value):
253 rounded_up = value + 4095
254 return rounded_up - (rounded_up % 4096)
255
256
Ying Wang7e6d4e42010-12-13 16:25:36 -0800257def CloseInheritedPipes():
258 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
259 before doing other work."""
260 if platform.system() != "Darwin":
261 return
262 for d in range(3, 1025):
263 try:
264 stat = os.fstat(d)
265 if stat is not None:
266 pipebit = stat[0] & 0x1000
267 if pipebit != 0:
268 os.close(d)
269 except OSError:
270 pass
271
272
Tao Bao410ad8b2018-08-24 12:08:38 -0700273def LoadInfoDict(input_file, repacking=False):
274 """Loads the key/value pairs from the given input target_files.
275
276 It reads `META/misc_info.txt` file in the target_files input, does sanity
277 checks and returns the parsed key/value pairs for to the given build. It's
278 usually called early when working on input target_files files, e.g. when
279 generating OTAs, or signing builds. Note that the function may be called
280 against an old target_files file (i.e. from past dessert releases). So the
281 property parsing needs to be backward compatible.
282
283 In a `META/misc_info.txt`, a few properties are stored as links to the files
284 in the PRODUCT_OUT directory. It works fine with the build system. However,
285 they are no longer available when (re)generating images from target_files zip.
286 When `repacking` is True, redirect these properties to the actual files in the
287 unzipped directory.
288
289 Args:
290 input_file: The input target_files file, which could be an open
291 zipfile.ZipFile instance, or a str for the dir that contains the files
292 unzipped from a target_files file.
293 repacking: Whether it's trying repack an target_files file after loading the
294 info dict (default: False). If so, it will rewrite a few loaded
295 properties (e.g. selinux_fc, root_dir) to point to the actual files in
296 target_files file. When doing repacking, `input_file` must be a dir.
297
298 Returns:
299 A dict that contains the parsed key/value pairs.
300
301 Raises:
302 AssertionError: On invalid input arguments.
303 ValueError: On malformed input values.
304 """
305 if repacking:
306 assert isinstance(input_file, str), \
307 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700308
Doug Zongkerc9253822014-02-04 12:17:58 -0800309 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700310 if isinstance(input_file, zipfile.ZipFile):
311 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800312 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700313 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800314 try:
315 with open(path) as f:
316 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700317 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800318 if e.errno == errno.ENOENT:
319 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800320
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700321 try:
Michael Runge6e836112014-04-15 17:40:21 -0700322 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700323 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700324 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700325
Tao Bao410ad8b2018-08-24 12:08:38 -0700326 if "recovery_api_version" not in d:
327 raise ValueError("Failed to find 'recovery_api_version'")
328 if "fstab_version" not in d:
329 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800330
Tao Bao410ad8b2018-08-24 12:08:38 -0700331 if repacking:
332 # We carry a copy of file_contexts.bin under META/. If not available, search
333 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
334 # images than the one running on device, in that case, we must have the one
335 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700336 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700337 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700338 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700339
Tom Cherryd14b8952018-08-09 14:26:00 -0700340 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700341
Tom Cherryd14b8952018-08-09 14:26:00 -0700342 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700343 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700344 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700345 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700346
Tao Baof54216f2016-03-29 15:12:37 -0700347 # Redirect {system,vendor}_base_fs_file.
348 if "system_base_fs_file" in d:
349 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700350 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700351 if os.path.exists(system_base_fs_file):
352 d["system_base_fs_file"] = system_base_fs_file
353 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700354 logger.warning(
355 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700356 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700357
358 if "vendor_base_fs_file" in d:
359 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700360 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700361 if os.path.exists(vendor_base_fs_file):
362 d["vendor_base_fs_file"] = vendor_base_fs_file
363 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700364 logger.warning(
365 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700366 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700367
Doug Zongker37974732010-09-16 17:44:38 -0700368 def makeint(key):
369 if key in d:
370 d[key] = int(d[key], 0)
371
372 makeint("recovery_api_version")
373 makeint("blocksize")
374 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700375 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700376 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700377 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700378 makeint("recovery_size")
379 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800380 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700381
Tao Baoa57ab9f2018-08-24 12:08:38 -0700382 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
383 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
384 # cases, since it may load the info_dict from an old build (e.g. when
385 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800386 system_root_image = d.get("system_root_image") == "true"
387 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700388 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700389 if isinstance(input_file, zipfile.ZipFile):
390 if recovery_fstab_path not in input_file.namelist():
391 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
392 else:
393 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
394 if not os.path.exists(path):
395 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800396 d["fstab"] = LoadRecoveryFSTab(
397 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700398
Tao Bao76def242017-11-21 09:25:31 -0800399 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700400 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700401 if isinstance(input_file, zipfile.ZipFile):
402 if recovery_fstab_path not in input_file.namelist():
403 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
404 else:
405 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
406 if not os.path.exists(path):
407 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800408 d["fstab"] = LoadRecoveryFSTab(
409 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700410
Tianjie Xucfa86222016-03-07 16:31:19 -0800411 else:
412 d["fstab"] = None
413
Tianjie Xu861f4132018-09-12 11:49:33 -0700414 # Tries to load the build props for all partitions with care_map, including
415 # system and vendor.
416 for partition in PARTITIONS_WITH_CARE_MAP:
417 d["{}.build.prop".format(partition)] = LoadBuildProp(
418 read_helper, "{}/build.prop".format(partition.upper()))
419 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800420
421 # Set up the salt (based on fingerprint or thumbprint) that will be used when
422 # adding AVB footer.
423 if d.get("avb_enable") == "true":
424 fp = None
425 if "build.prop" in d:
426 build_prop = d["build.prop"]
427 if "ro.build.fingerprint" in build_prop:
428 fp = build_prop["ro.build.fingerprint"]
429 elif "ro.build.thumbprint" in build_prop:
430 fp = build_prop["ro.build.thumbprint"]
431 if fp:
432 d["avb_salt"] = sha256(fp).hexdigest()
433
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700434 return d
435
Tao Baod1de6f32017-03-01 16:38:48 -0800436
Tao Baobcd1d162017-08-26 13:10:26 -0700437def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700438 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700439 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700440 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700441 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700442 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700443 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700444
Tao Baod1de6f32017-03-01 16:38:48 -0800445
Michael Runge6e836112014-04-15 17:40:21 -0700446def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700447 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700448 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700449 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700450 if not line or line.startswith("#"):
451 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700452 if "=" in line:
453 name, value = line.split("=", 1)
454 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700455 return d
456
Tao Baod1de6f32017-03-01 16:38:48 -0800457
Tianjie Xucfa86222016-03-07 16:31:19 -0800458def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
459 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700460 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800461 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700462 self.mount_point = mount_point
463 self.fs_type = fs_type
464 self.device = device
465 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700466 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700467
468 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800469 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700470 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700471 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700472 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700473
Tao Baod1de6f32017-03-01 16:38:48 -0800474 assert fstab_version == 2
475
476 d = {}
477 for line in data.split("\n"):
478 line = line.strip()
479 if not line or line.startswith("#"):
480 continue
481
482 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
483 pieces = line.split()
484 if len(pieces) != 5:
485 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
486
487 # Ignore entries that are managed by vold.
488 options = pieces[4]
489 if "voldmanaged=" in options:
490 continue
491
492 # It's a good line, parse it.
493 length = 0
494 options = options.split(",")
495 for i in options:
496 if i.startswith("length="):
497 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800498 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800499 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700500 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800501
Tao Baod1de6f32017-03-01 16:38:48 -0800502 mount_flags = pieces[3]
503 # Honor the SELinux context if present.
504 context = None
505 for i in mount_flags.split(","):
506 if i.startswith("context="):
507 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800508
Tao Baod1de6f32017-03-01 16:38:48 -0800509 mount_point = pieces[1]
510 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
511 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800512
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700513 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700514 # system. Other areas assume system is always at "/system" so point /system
515 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700516 if system_root_image:
517 assert not d.has_key("/system") and d.has_key("/")
518 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700519 return d
520
521
Doug Zongker37974732010-09-16 17:44:38 -0700522def DumpInfoDict(d):
523 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700524 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700525
Dan Albert8b72aef2015-03-23 19:13:21 -0700526
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800527def AppendAVBSigningArgs(cmd, partition):
528 """Append signing arguments for avbtool."""
529 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
530 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
531 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
532 if key_path and algorithm:
533 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700534 avb_salt = OPTIONS.info_dict.get("avb_salt")
535 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700536 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700537 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800538
539
Tao Bao02a08592018-07-22 12:40:45 -0700540def GetAvbChainedPartitionArg(partition, info_dict, key=None):
541 """Constructs and returns the arg to build or verify a chained partition.
542
543 Args:
544 partition: The partition name.
545 info_dict: The info dict to look up the key info and rollback index
546 location.
547 key: The key to be used for building or verifying the partition. Defaults to
548 the key listed in info_dict.
549
550 Returns:
551 A string of form "partition:rollback_index_location:key" that can be used to
552 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700553 """
554 if key is None:
555 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao2cc0ca12019-03-15 10:44:43 -0700556 pubkey_path = ExtractAvbPublicKey(key)
Tao Bao02a08592018-07-22 12:40:45 -0700557 rollback_index_location = info_dict[
558 "avb_" + partition + "_rollback_index_location"]
559 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
560
561
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700562def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800563 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700564 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700565
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700566 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800567 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
568 we are building a two-step special image (i.e. building a recovery image to
569 be loaded into /boot in two-step OTAs).
570
571 Return the image data, or None if sourcedir does not appear to contains files
572 for building the requested image.
573 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700574
575 def make_ramdisk():
576 ramdisk_img = tempfile.NamedTemporaryFile()
577
578 if os.access(fs_config_file, os.F_OK):
579 cmd = ["mkbootfs", "-f", fs_config_file,
580 os.path.join(sourcedir, "RAMDISK")]
581 else:
582 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
583 p1 = Run(cmd, stdout=subprocess.PIPE)
584 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
585
586 p2.wait()
587 p1.wait()
588 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
589 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
590
591 return ramdisk_img
592
593 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
594 return None
595
596 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700597 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700598
Doug Zongkerd5131602012-08-02 14:46:42 -0700599 if info_dict is None:
600 info_dict = OPTIONS.info_dict
601
Doug Zongkereef39442009-04-02 12:14:19 -0700602 img = tempfile.NamedTemporaryFile()
603
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700604 if has_ramdisk:
605 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700606
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800607 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
608 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
609
610 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700611
Benoit Fradina45a8682014-07-14 21:00:43 +0200612 fn = os.path.join(sourcedir, "second")
613 if os.access(fn, os.F_OK):
614 cmd.append("--second")
615 cmd.append(fn)
616
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800617 fn = os.path.join(sourcedir, "dtb")
618 if os.access(fn, os.F_OK):
619 cmd.append("--dtb")
620 cmd.append(fn)
621
Doug Zongker171f1cd2009-06-15 22:36:37 -0700622 fn = os.path.join(sourcedir, "cmdline")
623 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700624 cmd.append("--cmdline")
625 cmd.append(open(fn).read().rstrip("\n"))
626
627 fn = os.path.join(sourcedir, "base")
628 if os.access(fn, os.F_OK):
629 cmd.append("--base")
630 cmd.append(open(fn).read().rstrip("\n"))
631
Ying Wang4de6b5b2010-08-25 14:29:34 -0700632 fn = os.path.join(sourcedir, "pagesize")
633 if os.access(fn, os.F_OK):
634 cmd.append("--pagesize")
635 cmd.append(open(fn).read().rstrip("\n"))
636
Tao Bao76def242017-11-21 09:25:31 -0800637 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700638 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700639 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700640
Tao Bao76def242017-11-21 09:25:31 -0800641 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000642 if args and args.strip():
643 cmd.extend(shlex.split(args))
644
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700645 if has_ramdisk:
646 cmd.extend(["--ramdisk", ramdisk_img.name])
647
Tao Baod95e9fd2015-03-29 23:07:41 -0700648 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800649 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700650 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700651 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700652 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700653 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700654
Tao Baobf70c3182017-07-11 17:27:55 -0700655 # "boot" or "recovery", without extension.
656 partition_name = os.path.basename(sourcedir).lower()
657
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800658 if partition_name == "recovery":
659 if info_dict.get("include_recovery_dtbo") == "true":
660 fn = os.path.join(sourcedir, "recovery_dtbo")
661 cmd.extend(["--recovery_dtbo", fn])
662 if info_dict.get("include_recovery_acpio") == "true":
663 fn = os.path.join(sourcedir, "recovery_acpio")
664 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700665
Tao Bao986ee862018-10-04 15:46:16 -0700666 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700667
Tao Bao76def242017-11-21 09:25:31 -0800668 if (info_dict.get("boot_signer") == "true" and
669 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800670 # Hard-code the path as "/boot" for two-step special recovery image (which
671 # will be loaded into /boot during the two-step OTA).
672 if two_step_image:
673 path = "/boot"
674 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700675 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700676 cmd = [OPTIONS.boot_signer_path]
677 cmd.extend(OPTIONS.boot_signer_args)
678 cmd.extend([path, img.name,
679 info_dict["verity_key"] + ".pk8",
680 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700681 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700682
Tao Baod95e9fd2015-03-29 23:07:41 -0700683 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800684 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700685 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700686 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800687 # We have switched from the prebuilt futility binary to using the tool
688 # (futility-host) built from the source. Override the setting in the old
689 # TF.zip.
690 futility = info_dict["futility"]
691 if futility.startswith("prebuilts/"):
692 futility = "futility-host"
693 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700694 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700695 info_dict["vboot_key"] + ".vbprivk",
696 info_dict["vboot_subkey"] + ".vbprivk",
697 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700698 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700699 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700700
Tao Baof3282b42015-04-01 11:21:55 -0700701 # Clean up the temp files.
702 img_unsigned.close()
703 img_keyblock.close()
704
David Zeuthen8fecb282017-12-01 16:24:01 -0500705 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800706 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700707 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500708 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400709 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700710 "--partition_size", str(part_size), "--partition_name",
711 partition_name]
712 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500713 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400714 if args and args.strip():
715 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700716 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500717
718 img.seek(os.SEEK_SET, 0)
719 data = img.read()
720
721 if has_ramdisk:
722 ramdisk_img.close()
723 img.close()
724
725 return data
726
727
Doug Zongkerd5131602012-08-02 14:46:42 -0700728def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800729 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700730 """Return a File object with the desired bootable image.
731
732 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
733 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
734 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700735
Doug Zongker55d93282011-01-25 17:03:34 -0800736 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
737 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700738 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800739 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700740
741 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
742 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700743 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700744 return File.FromLocalFile(name, prebuilt_path)
745
Tao Bao32fcdab2018-10-12 10:30:39 -0700746 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700747
748 if info_dict is None:
749 info_dict = OPTIONS.info_dict
750
751 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800752 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
753 # for recovery.
754 has_ramdisk = (info_dict.get("system_root_image") != "true" or
755 prebuilt_name != "boot.img" or
756 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700757
Doug Zongker6f1d0312014-08-22 08:07:12 -0700758 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400759 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
760 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800761 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700762 if data:
763 return File(name, data)
764 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800765
Doug Zongkereef39442009-04-02 12:14:19 -0700766
Narayan Kamatha07bf042017-08-14 14:49:21 +0100767def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800768 """Gunzips the given gzip compressed file to a given output file."""
769 with gzip.open(in_filename, "rb") as in_file, \
770 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100771 shutil.copyfileobj(in_file, out_file)
772
773
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800774def UnzipToDir(filename, dirname, pattern=None):
775 """Unzips the archive to the given directory.
776
777 Args:
778 filename: The name of the zip file to unzip.
779
780 dirname: Where the unziped files will land.
781
782 pattern: Files to unzip from the archive. If omitted, will unzip the entire
783 archvie.
784 """
785
786 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
787 if pattern is not None:
788 cmd.extend(pattern)
789 RunAndCheckOutput(cmd)
790
791
Doug Zongker75f17362009-12-08 13:46:44 -0800792def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800793 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800794
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800795 Args:
796 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
797 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
798
799 pattern: Files to unzip from the archive. If omitted, will unzip the entire
800 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800801
Tao Bao1c830bf2017-12-25 10:43:47 -0800802 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800803 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800804 """
Doug Zongkereef39442009-04-02 12:14:19 -0700805
Tao Bao1c830bf2017-12-25 10:43:47 -0800806 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800807 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
808 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800809 UnzipToDir(m.group(1), tmp, pattern)
810 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800811 filename = m.group(1)
812 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800813 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800814
Tao Baodba59ee2018-01-09 13:21:02 -0800815 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700816
817
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700818def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
819 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800820 """Returns a SparseImage object suitable for passing to BlockImageDiff.
821
822 This function loads the specified sparse image from the given path, and
823 performs additional processing for OTA purpose. For example, it always adds
824 block 0 to clobbered blocks list. It also detects files that cannot be
825 reconstructed from the block list, for whom we should avoid applying imgdiff.
826
827 Args:
828 which: The partition name, which must be "system" or "vendor".
829 tmpdir: The directory that contains the prebuilt image and block map file.
830 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800831 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700832 hashtree_info_generator: If present, generates the hashtree_info for this
833 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800834 Returns:
835 A SparseImage object, with file_map info loaded.
836 """
837 assert which in ("system", "vendor")
838
839 path = os.path.join(tmpdir, "IMAGES", which + ".img")
840 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
841
842 # The image and map files must have been created prior to calling
843 # ota_from_target_files.py (since LMP).
844 assert os.path.exists(path) and os.path.exists(mappath)
845
846 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
847 # it to clobbered_blocks so that it will be written to the target
848 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
849 clobbered_blocks = "0"
850
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700851 image = sparse_img.SparseImage(
852 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
853 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800854
855 # block.map may contain less blocks, because mke2fs may skip allocating blocks
856 # if they contain all zeros. We can't reconstruct such a file from its block
857 # list. Tag such entries accordingly. (Bug: 65213616)
858 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800859 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700860 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800861 continue
862
Tom Cherryd14b8952018-08-09 14:26:00 -0700863 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
864 # filename listed in system.map may contain an additional leading slash
865 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
866 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700867 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
868
Tom Cherryd14b8952018-08-09 14:26:00 -0700869 # Special handling another case, where files not under /system
870 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700871 if which == 'system' and not arcname.startswith('SYSTEM'):
872 arcname = 'ROOT/' + arcname
873
874 assert arcname in input_zip.namelist(), \
875 "Failed to find the ZIP entry for {}".format(entry)
876
Tao Baoc765cca2018-01-31 17:32:40 -0800877 info = input_zip.getinfo(arcname)
878 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800879
880 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800881 # image, check the original block list to determine its completeness. Note
882 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800883 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800884 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800885
Tao Baoc765cca2018-01-31 17:32:40 -0800886 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
887 ranges.extra['incomplete'] = True
888
889 return image
890
891
Doug Zongkereef39442009-04-02 12:14:19 -0700892def GetKeyPasswords(keylist):
893 """Given a list of keys, prompt the user to enter passwords for
894 those which require them. Return a {key: password} dict. password
895 will be None if the key has no password."""
896
Doug Zongker8ce7c252009-05-22 13:34:54 -0700897 no_passwords = []
898 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700899 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700900 devnull = open("/dev/null", "w+b")
901 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800902 # We don't need a password for things that aren't really keys.
903 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700904 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700905 continue
906
T.R. Fullhart37e10522013-03-18 10:31:26 -0700907 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700908 "-inform", "DER", "-nocrypt"],
909 stdin=devnull.fileno(),
910 stdout=devnull.fileno(),
911 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700912 p.communicate()
913 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700914 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700915 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700916 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700917 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
918 "-inform", "DER", "-passin", "pass:"],
919 stdin=devnull.fileno(),
920 stdout=devnull.fileno(),
921 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700922 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700923 if p.returncode == 0:
924 # Encrypted key with empty string as password.
925 key_passwords[k] = ''
926 elif stderr.startswith('Error decrypting key'):
927 # Definitely encrypted key.
928 # It would have said "Error reading key" if it didn't parse correctly.
929 need_passwords.append(k)
930 else:
931 # Potentially, a type of key that openssl doesn't understand.
932 # We'll let the routines in signapk.jar handle it.
933 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700934 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700935
T.R. Fullhart37e10522013-03-18 10:31:26 -0700936 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800937 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700938 return key_passwords
939
940
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800941def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700942 """Gets the minSdkVersion declared in the APK.
943
944 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
945 This can be both a decimal number (API Level) or a codename.
946
947 Args:
948 apk_name: The APK filename.
949
950 Returns:
951 The parsed SDK version string.
952
953 Raises:
954 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800955 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700956 proc = Run(
957 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
958 stderr=subprocess.PIPE)
959 stdoutdata, stderrdata = proc.communicate()
960 if proc.returncode != 0:
961 raise ExternalError(
962 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
963 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800964
Tao Baof47bf0f2018-03-21 23:28:51 -0700965 for line in stdoutdata.split("\n"):
966 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800967 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
968 if m:
969 return m.group(1)
970 raise ExternalError("No minSdkVersion returned by aapt")
971
972
973def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700974 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800975
Tao Baof47bf0f2018-03-21 23:28:51 -0700976 If minSdkVersion is set to a codename, it is translated to a number using the
977 provided map.
978
979 Args:
980 apk_name: The APK filename.
981
982 Returns:
983 The parsed SDK version number.
984
985 Raises:
986 ExternalError: On failing to get the min SDK version number.
987 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800988 version = GetMinSdkVersion(apk_name)
989 try:
990 return int(version)
991 except ValueError:
992 # Not a decimal number. Codename?
993 if version in codename_to_api_level_map:
994 return codename_to_api_level_map[version]
995 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700996 raise ExternalError(
997 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
998 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800999
1000
1001def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -08001002 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -07001003 """Sign the input_name zip/jar/apk, producing output_name. Use the
1004 given key and password (the latter may be None if the key does not
1005 have a password.
1006
Doug Zongker951495f2009-08-14 12:44:19 -07001007 If whole_file is true, use the "-w" option to SignApk to embed a
1008 signature that covers the whole file in the archive comment of the
1009 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001010
1011 min_api_level is the API Level (int) of the oldest platform this file may end
1012 up on. If not specified for an APK, the API Level is obtained by interpreting
1013 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1014
1015 codename_to_api_level_map is needed to translate the codename which may be
1016 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -07001017 """
Tao Bao76def242017-11-21 09:25:31 -08001018 if codename_to_api_level_map is None:
1019 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -07001020
Alex Klyubin9667b182015-12-10 13:38:50 -08001021 java_library_path = os.path.join(
1022 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1023
Tao Baoe95540e2016-11-08 12:08:53 -08001024 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1025 ["-Djava.library.path=" + java_library_path,
1026 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
1027 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001028 if whole_file:
1029 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001030
1031 min_sdk_version = min_api_level
1032 if min_sdk_version is None:
1033 if not whole_file:
1034 min_sdk_version = GetMinSdkVersionInt(
1035 input_name, codename_to_api_level_map)
1036 if min_sdk_version is not None:
1037 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1038
T.R. Fullhart37e10522013-03-18 10:31:26 -07001039 cmd.extend([key + OPTIONS.public_key_suffix,
1040 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001041 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001042
Tao Bao73dd4f42018-10-04 16:25:33 -07001043 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001044 if password is not None:
1045 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001046 stdoutdata, _ = proc.communicate(password)
1047 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001048 raise ExternalError(
1049 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001050 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001051
Doug Zongkereef39442009-04-02 12:14:19 -07001052
Doug Zongker37974732010-09-16 17:44:38 -07001053def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001054 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001055
Tao Bao9dd909e2017-11-14 11:27:32 -08001056 For non-AVB images, raise exception if the data is too big. Print a warning
1057 if the data is nearing the maximum size.
1058
1059 For AVB images, the actual image size should be identical to the limit.
1060
1061 Args:
1062 data: A string that contains all the data for the partition.
1063 target: The partition name. The ".img" suffix is optional.
1064 info_dict: The dict to be looked up for relevant info.
1065 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001066 if target.endswith(".img"):
1067 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001068 mount_point = "/" + target
1069
Ying Wangf8824af2014-06-03 14:07:27 -07001070 fs_type = None
1071 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001072 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001073 if mount_point == "/userdata":
1074 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001075 p = info_dict["fstab"][mount_point]
1076 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001077 device = p.device
1078 if "/" in device:
1079 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001080 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001081 if not fs_type or not limit:
1082 return
Doug Zongkereef39442009-04-02 12:14:19 -07001083
Andrew Boie0f9aec82012-02-14 09:32:52 -08001084 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001085 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1086 # path.
1087 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1088 if size != limit:
1089 raise ExternalError(
1090 "Mismatching image size for %s: expected %d actual %d" % (
1091 target, limit, size))
1092 else:
1093 pct = float(size) * 100.0 / limit
1094 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1095 if pct >= 99.0:
1096 raise ExternalError(msg)
1097 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001098 logger.warning("\n WARNING: %s\n", msg)
1099 else:
1100 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001101
1102
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001103def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001104 """Parses the APK certs info from a given target-files zip.
1105
1106 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1107 tuple with the following elements: (1) a dictionary that maps packages to
1108 certs (based on the "certificate" and "private_key" attributes in the file;
1109 (2) a string representing the extension of compressed APKs in the target files
1110 (e.g ".gz", ".bro").
1111
1112 Args:
1113 tf_zip: The input target_files ZipFile (already open).
1114
1115 Returns:
1116 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1117 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1118 no compressed APKs.
1119 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001120 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001121 compressed_extension = None
1122
Tao Bao0f990332017-09-08 19:02:54 -07001123 # META/apkcerts.txt contains the info for _all_ the packages known at build
1124 # time. Filter out the ones that are not installed.
1125 installed_files = set()
1126 for name in tf_zip.namelist():
1127 basename = os.path.basename(name)
1128 if basename:
1129 installed_files.add(basename)
1130
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001131 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1132 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001133 if not line:
1134 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001135 m = re.match(
1136 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1137 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1138 line)
1139 if not m:
1140 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001141
Tao Bao818ddf52018-01-05 11:17:34 -08001142 matches = m.groupdict()
1143 cert = matches["CERT"]
1144 privkey = matches["PRIVKEY"]
1145 name = matches["NAME"]
1146 this_compressed_extension = matches["COMPRESSED"]
1147
1148 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1149 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1150 if cert in SPECIAL_CERT_STRINGS and not privkey:
1151 certmap[name] = cert
1152 elif (cert.endswith(OPTIONS.public_key_suffix) and
1153 privkey.endswith(OPTIONS.private_key_suffix) and
1154 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1155 certmap[name] = cert[:-public_key_suffix_len]
1156 else:
1157 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1158
1159 if not this_compressed_extension:
1160 continue
1161
1162 # Only count the installed files.
1163 filename = name + '.' + this_compressed_extension
1164 if filename not in installed_files:
1165 continue
1166
1167 # Make sure that all the values in the compression map have the same
1168 # extension. We don't support multiple compression methods in the same
1169 # system image.
1170 if compressed_extension:
1171 if this_compressed_extension != compressed_extension:
1172 raise ValueError(
1173 "Multiple compressed extensions: {} vs {}".format(
1174 compressed_extension, this_compressed_extension))
1175 else:
1176 compressed_extension = this_compressed_extension
1177
1178 return (certmap,
1179 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001180
1181
Doug Zongkereef39442009-04-02 12:14:19 -07001182COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001183Global options
1184
1185 -p (--path) <dir>
1186 Prepend <dir>/bin to the list of places to search for binaries run by this
1187 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001188
Doug Zongker05d3dea2009-06-22 11:32:31 -07001189 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001190 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001191
Tao Bao30df8b42018-04-23 15:32:53 -07001192 -x (--extra) <key=value>
1193 Add a key/value pair to the 'extras' dict, which device-specific extension
1194 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001195
Doug Zongkereef39442009-04-02 12:14:19 -07001196 -v (--verbose)
1197 Show command lines being executed.
1198
1199 -h (--help)
1200 Display this usage message and exit.
1201"""
1202
1203def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001204 print(docstring.rstrip("\n"))
1205 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001206
1207
1208def ParseOptions(argv,
1209 docstring,
1210 extra_opts="", extra_long_opts=(),
1211 extra_option_handler=None):
1212 """Parse the options in argv and return any arguments that aren't
1213 flags. docstring is the calling module's docstring, to be displayed
1214 for errors and -h. extra_opts and extra_long_opts are for flags
1215 defined by the caller, which are processed by passing them to
1216 extra_option_handler."""
1217
1218 try:
1219 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001220 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001221 ["help", "verbose", "path=", "signapk_path=",
1222 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001223 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001224 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1225 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001226 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001227 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001228 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001229 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001230 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001231 sys.exit(2)
1232
Doug Zongkereef39442009-04-02 12:14:19 -07001233 for o, a in opts:
1234 if o in ("-h", "--help"):
1235 Usage(docstring)
1236 sys.exit()
1237 elif o in ("-v", "--verbose"):
1238 OPTIONS.verbose = True
1239 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001240 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001241 elif o in ("--signapk_path",):
1242 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001243 elif o in ("--signapk_shared_library_path",):
1244 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001245 elif o in ("--extra_signapk_args",):
1246 OPTIONS.extra_signapk_args = shlex.split(a)
1247 elif o in ("--java_path",):
1248 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001249 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001250 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001251 elif o in ("--public_key_suffix",):
1252 OPTIONS.public_key_suffix = a
1253 elif o in ("--private_key_suffix",):
1254 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001255 elif o in ("--boot_signer_path",):
1256 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001257 elif o in ("--boot_signer_args",):
1258 OPTIONS.boot_signer_args = shlex.split(a)
1259 elif o in ("--verity_signer_path",):
1260 OPTIONS.verity_signer_path = a
1261 elif o in ("--verity_signer_args",):
1262 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001263 elif o in ("-s", "--device_specific"):
1264 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001265 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001266 key, value = a.split("=", 1)
1267 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001268 else:
1269 if extra_option_handler is None or not extra_option_handler(o, a):
1270 assert False, "unknown option \"%s\"" % (o,)
1271
Doug Zongker85448772014-09-09 14:59:20 -07001272 if OPTIONS.search_path:
1273 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1274 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001275
1276 return args
1277
1278
Tao Bao4c851b12016-09-19 13:54:38 -07001279def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001280 """Make a temp file and add it to the list of things to be deleted
1281 when Cleanup() is called. Return the filename."""
1282 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1283 os.close(fd)
1284 OPTIONS.tempfiles.append(fn)
1285 return fn
1286
1287
Tao Bao1c830bf2017-12-25 10:43:47 -08001288def MakeTempDir(prefix='tmp', suffix=''):
1289 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1290
1291 Returns:
1292 The absolute pathname of the new directory.
1293 """
1294 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1295 OPTIONS.tempfiles.append(dir_name)
1296 return dir_name
1297
1298
Doug Zongkereef39442009-04-02 12:14:19 -07001299def Cleanup():
1300 for i in OPTIONS.tempfiles:
1301 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001302 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001303 else:
1304 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001305 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001306
1307
1308class PasswordManager(object):
1309 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001310 self.editor = os.getenv("EDITOR")
1311 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001312
1313 def GetPasswords(self, items):
1314 """Get passwords corresponding to each string in 'items',
1315 returning a dict. (The dict may have keys in addition to the
1316 values in 'items'.)
1317
1318 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1319 user edit that file to add more needed passwords. If no editor is
1320 available, or $ANDROID_PW_FILE isn't define, prompts the user
1321 interactively in the ordinary way.
1322 """
1323
1324 current = self.ReadFile()
1325
1326 first = True
1327 while True:
1328 missing = []
1329 for i in items:
1330 if i not in current or not current[i]:
1331 missing.append(i)
1332 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001333 if not missing:
1334 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001335
1336 for i in missing:
1337 current[i] = ""
1338
1339 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001340 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001341 answer = raw_input("try to edit again? [y]> ").strip()
1342 if answer and answer[0] not in 'yY':
1343 raise RuntimeError("key passwords unavailable")
1344 first = False
1345
1346 current = self.UpdateAndReadFile(current)
1347
Dan Albert8b72aef2015-03-23 19:13:21 -07001348 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001349 """Prompt the user to enter a value (password) for each key in
1350 'current' whose value is fales. Returns a new dict with all the
1351 values.
1352 """
1353 result = {}
1354 for k, v in sorted(current.iteritems()):
1355 if v:
1356 result[k] = v
1357 else:
1358 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001359 result[k] = getpass.getpass(
1360 "Enter password for %s key> " % k).strip()
1361 if result[k]:
1362 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001363 return result
1364
1365 def UpdateAndReadFile(self, current):
1366 if not self.editor or not self.pwfile:
1367 return self.PromptResult(current)
1368
1369 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001370 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001371 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1372 f.write("# (Additional spaces are harmless.)\n\n")
1373
1374 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001375 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1376 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001377 f.write("[[[ %s ]]] %s\n" % (v, k))
1378 if not v and first_line is None:
1379 # position cursor on first line with no password.
1380 first_line = i + 4
1381 f.close()
1382
Tao Bao986ee862018-10-04 15:46:16 -07001383 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001384
1385 return self.ReadFile()
1386
1387 def ReadFile(self):
1388 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001389 if self.pwfile is None:
1390 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001391 try:
1392 f = open(self.pwfile, "r")
1393 for line in f:
1394 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001395 if not line or line[0] == '#':
1396 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001397 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1398 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001399 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001400 else:
1401 result[m.group(2)] = m.group(1)
1402 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001403 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001404 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001405 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001406 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001407
1408
Dan Albert8e0178d2015-01-27 15:53:15 -08001409def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1410 compress_type=None):
1411 import datetime
1412
1413 # http://b/18015246
1414 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1415 # for files larger than 2GiB. We can work around this by adjusting their
1416 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1417 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1418 # it isn't clear to me exactly what circumstances cause this).
1419 # `zipfile.write()` must be used directly to work around this.
1420 #
1421 # This mess can be avoided if we port to python3.
1422 saved_zip64_limit = zipfile.ZIP64_LIMIT
1423 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1424
1425 if compress_type is None:
1426 compress_type = zip_file.compression
1427 if arcname is None:
1428 arcname = filename
1429
1430 saved_stat = os.stat(filename)
1431
1432 try:
1433 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1434 # file to be zipped and reset it when we're done.
1435 os.chmod(filename, perms)
1436
1437 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001438 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1439 # intentional. zip stores datetimes in local time without a time zone
1440 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1441 # in the zip archive.
1442 local_epoch = datetime.datetime.fromtimestamp(0)
1443 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001444 os.utime(filename, (timestamp, timestamp))
1445
1446 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1447 finally:
1448 os.chmod(filename, saved_stat.st_mode)
1449 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1450 zipfile.ZIP64_LIMIT = saved_zip64_limit
1451
1452
Tao Bao58c1b962015-05-20 09:32:18 -07001453def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001454 compress_type=None):
1455 """Wrap zipfile.writestr() function to work around the zip64 limit.
1456
1457 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1458 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1459 when calling crc32(bytes).
1460
1461 But it still works fine to write a shorter string into a large zip file.
1462 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1463 when we know the string won't be too long.
1464 """
1465
1466 saved_zip64_limit = zipfile.ZIP64_LIMIT
1467 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1468
1469 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1470 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001471 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001472 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001473 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001474 else:
Tao Baof3282b42015-04-01 11:21:55 -07001475 zinfo = zinfo_or_arcname
1476
1477 # If compress_type is given, it overrides the value in zinfo.
1478 if compress_type is not None:
1479 zinfo.compress_type = compress_type
1480
Tao Bao58c1b962015-05-20 09:32:18 -07001481 # If perms is given, it has a priority.
1482 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001483 # If perms doesn't set the file type, mark it as a regular file.
1484 if perms & 0o770000 == 0:
1485 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001486 zinfo.external_attr = perms << 16
1487
Tao Baof3282b42015-04-01 11:21:55 -07001488 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001489 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1490
Dan Albert8b72aef2015-03-23 19:13:21 -07001491 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001492 zipfile.ZIP64_LIMIT = saved_zip64_limit
1493
1494
Tao Bao89d7ab22017-12-14 17:05:33 -08001495def ZipDelete(zip_filename, entries):
1496 """Deletes entries from a ZIP file.
1497
1498 Since deleting entries from a ZIP file is not supported, it shells out to
1499 'zip -d'.
1500
1501 Args:
1502 zip_filename: The name of the ZIP file.
1503 entries: The name of the entry, or the list of names to be deleted.
1504
1505 Raises:
1506 AssertionError: In case of non-zero return from 'zip'.
1507 """
1508 if isinstance(entries, basestring):
1509 entries = [entries]
1510 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001511 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001512
1513
Tao Baof3282b42015-04-01 11:21:55 -07001514def ZipClose(zip_file):
1515 # http://b/18015246
1516 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1517 # central directory.
1518 saved_zip64_limit = zipfile.ZIP64_LIMIT
1519 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1520
1521 zip_file.close()
1522
1523 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001524
1525
1526class DeviceSpecificParams(object):
1527 module = None
1528 def __init__(self, **kwargs):
1529 """Keyword arguments to the constructor become attributes of this
1530 object, which is passed to all functions in the device-specific
1531 module."""
1532 for k, v in kwargs.iteritems():
1533 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001534 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001535
1536 if self.module is None:
1537 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001538 if not path:
1539 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001540 try:
1541 if os.path.isdir(path):
1542 info = imp.find_module("releasetools", [path])
1543 else:
1544 d, f = os.path.split(path)
1545 b, x = os.path.splitext(f)
1546 if x == ".py":
1547 f = b
1548 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001549 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001550 self.module = imp.load_module("device_specific", *info)
1551 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001552 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001553
1554 def _DoCall(self, function_name, *args, **kwargs):
1555 """Call the named function in the device-specific module, passing
1556 the given args and kwargs. The first argument to the call will be
1557 the DeviceSpecific object itself. If there is no module, or the
1558 module does not define the function, return the value of the
1559 'default' kwarg (which itself defaults to None)."""
1560 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001561 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001562 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1563
1564 def FullOTA_Assertions(self):
1565 """Called after emitting the block of assertions at the top of a
1566 full OTA package. Implementations can add whatever additional
1567 assertions they like."""
1568 return self._DoCall("FullOTA_Assertions")
1569
Doug Zongkere5ff5902012-01-17 10:55:37 -08001570 def FullOTA_InstallBegin(self):
1571 """Called at the start of full OTA installation."""
1572 return self._DoCall("FullOTA_InstallBegin")
1573
Yifan Hong10c530d2018-12-27 17:34:18 -08001574 def FullOTA_GetBlockDifferences(self):
1575 """Called during full OTA installation and verification.
1576 Implementation should return a list of BlockDifference objects describing
1577 the update on each additional partitions.
1578 """
1579 return self._DoCall("FullOTA_GetBlockDifferences")
1580
Doug Zongker05d3dea2009-06-22 11:32:31 -07001581 def FullOTA_InstallEnd(self):
1582 """Called at the end of full OTA installation; typically this is
1583 used to install the image for the device's baseband processor."""
1584 return self._DoCall("FullOTA_InstallEnd")
1585
1586 def IncrementalOTA_Assertions(self):
1587 """Called after emitting the block of assertions at the top of an
1588 incremental OTA package. Implementations can add whatever
1589 additional assertions they like."""
1590 return self._DoCall("IncrementalOTA_Assertions")
1591
Doug Zongkere5ff5902012-01-17 10:55:37 -08001592 def IncrementalOTA_VerifyBegin(self):
1593 """Called at the start of the verification phase of incremental
1594 OTA installation; additional checks can be placed here to abort
1595 the script before any changes are made."""
1596 return self._DoCall("IncrementalOTA_VerifyBegin")
1597
Doug Zongker05d3dea2009-06-22 11:32:31 -07001598 def IncrementalOTA_VerifyEnd(self):
1599 """Called at the end of the verification phase of incremental OTA
1600 installation; additional checks can be placed here to abort the
1601 script before any changes are made."""
1602 return self._DoCall("IncrementalOTA_VerifyEnd")
1603
Doug Zongkere5ff5902012-01-17 10:55:37 -08001604 def IncrementalOTA_InstallBegin(self):
1605 """Called at the start of incremental OTA installation (after
1606 verification is complete)."""
1607 return self._DoCall("IncrementalOTA_InstallBegin")
1608
Yifan Hong10c530d2018-12-27 17:34:18 -08001609 def IncrementalOTA_GetBlockDifferences(self):
1610 """Called during incremental OTA installation and verification.
1611 Implementation should return a list of BlockDifference objects describing
1612 the update on each additional partitions.
1613 """
1614 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1615
Doug Zongker05d3dea2009-06-22 11:32:31 -07001616 def IncrementalOTA_InstallEnd(self):
1617 """Called at the end of incremental OTA installation; typically
1618 this is used to install the image for the device's baseband
1619 processor."""
1620 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001621
Tao Bao9bc6bb22015-11-09 16:58:28 -08001622 def VerifyOTA_Assertions(self):
1623 return self._DoCall("VerifyOTA_Assertions")
1624
Tao Bao76def242017-11-21 09:25:31 -08001625
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001626class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001627 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001628 self.name = name
1629 self.data = data
1630 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001631 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001632 self.sha1 = sha1(data).hexdigest()
1633
1634 @classmethod
1635 def FromLocalFile(cls, name, diskname):
1636 f = open(diskname, "rb")
1637 data = f.read()
1638 f.close()
1639 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001640
1641 def WriteToTemp(self):
1642 t = tempfile.NamedTemporaryFile()
1643 t.write(self.data)
1644 t.flush()
1645 return t
1646
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001647 def WriteToDir(self, d):
1648 with open(os.path.join(d, self.name), "wb") as fp:
1649 fp.write(self.data)
1650
Geremy Condra36bd3652014-02-06 19:45:10 -08001651 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001652 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001653
Tao Bao76def242017-11-21 09:25:31 -08001654
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001655DIFF_PROGRAM_BY_EXT = {
1656 ".gz" : "imgdiff",
1657 ".zip" : ["imgdiff", "-z"],
1658 ".jar" : ["imgdiff", "-z"],
1659 ".apk" : ["imgdiff", "-z"],
1660 ".img" : "imgdiff",
1661 }
1662
Tao Bao76def242017-11-21 09:25:31 -08001663
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001664class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001665 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001666 self.tf = tf
1667 self.sf = sf
1668 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001669 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001670
1671 def ComputePatch(self):
1672 """Compute the patch (as a string of data) needed to turn sf into
1673 tf. Returns the same tuple as GetPatch()."""
1674
1675 tf = self.tf
1676 sf = self.sf
1677
Doug Zongker24cd2802012-08-14 16:36:15 -07001678 if self.diff_program:
1679 diff_program = self.diff_program
1680 else:
1681 ext = os.path.splitext(tf.name)[1]
1682 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001683
1684 ttemp = tf.WriteToTemp()
1685 stemp = sf.WriteToTemp()
1686
1687 ext = os.path.splitext(tf.name)[1]
1688
1689 try:
1690 ptemp = tempfile.NamedTemporaryFile()
1691 if isinstance(diff_program, list):
1692 cmd = copy.copy(diff_program)
1693 else:
1694 cmd = [diff_program]
1695 cmd.append(stemp.name)
1696 cmd.append(ttemp.name)
1697 cmd.append(ptemp.name)
1698 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001699 err = []
1700 def run():
1701 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001702 if e:
1703 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001704 th = threading.Thread(target=run)
1705 th.start()
1706 th.join(timeout=300) # 5 mins
1707 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001708 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001709 p.terminate()
1710 th.join(5)
1711 if th.is_alive():
1712 p.kill()
1713 th.join()
1714
Tianjie Xua2a9f992018-01-05 15:15:54 -08001715 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001716 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001717 self.patch = None
1718 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001719 diff = ptemp.read()
1720 finally:
1721 ptemp.close()
1722 stemp.close()
1723 ttemp.close()
1724
1725 self.patch = diff
1726 return self.tf, self.sf, self.patch
1727
1728
1729 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001730 """Returns a tuple of (target_file, source_file, patch_data).
1731
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001732 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001733 computing the patch failed.
1734 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001735 return self.tf, self.sf, self.patch
1736
1737
1738def ComputeDifferences(diffs):
1739 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001740 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001741
1742 # Do the largest files first, to try and reduce the long-pole effect.
1743 by_size = [(i.tf.size, i) for i in diffs]
1744 by_size.sort(reverse=True)
1745 by_size = [i[1] for i in by_size]
1746
1747 lock = threading.Lock()
1748 diff_iter = iter(by_size) # accessed under lock
1749
1750 def worker():
1751 try:
1752 lock.acquire()
1753 for d in diff_iter:
1754 lock.release()
1755 start = time.time()
1756 d.ComputePatch()
1757 dur = time.time() - start
1758 lock.acquire()
1759
1760 tf, sf, patch = d.GetPatch()
1761 if sf.name == tf.name:
1762 name = tf.name
1763 else:
1764 name = "%s (%s)" % (tf.name, sf.name)
1765 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001766 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001767 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001768 logger.info(
1769 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1770 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001771 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001772 except Exception:
1773 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001774 raise
1775
1776 # start worker threads; wait for them all to finish.
1777 threads = [threading.Thread(target=worker)
1778 for i in range(OPTIONS.worker_threads)]
1779 for th in threads:
1780 th.start()
1781 while threads:
1782 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001783
1784
Dan Albert8b72aef2015-03-23 19:13:21 -07001785class BlockDifference(object):
1786 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001787 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001788 self.tgt = tgt
1789 self.src = src
1790 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001791 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001792 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001793
Tao Baodd2a5892015-03-12 12:32:37 -07001794 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001795 version = max(
1796 int(i) for i in
1797 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001798 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001799 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001800
1801 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001802 version=self.version,
1803 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001804 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001805 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001806 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001807 self.touched_src_ranges = b.touched_src_ranges
1808 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001809
Yifan Hong10c530d2018-12-27 17:34:18 -08001810 # On devices with dynamic partitions, for new partitions,
1811 # src is None but OPTIONS.source_info_dict is not.
1812 if OPTIONS.source_info_dict is None:
1813 is_dynamic_build = OPTIONS.info_dict.get(
1814 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001815 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001816 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001817 is_dynamic_build = OPTIONS.source_info_dict.get(
1818 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001819 is_dynamic_source = partition in shlex.split(
1820 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001821
Yifan Hongbb2658d2019-01-25 12:30:58 -08001822 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001823 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1824
Yifan Hongbb2658d2019-01-25 12:30:58 -08001825 # For dynamic partitions builds, check partition list in both source
1826 # and target build because new partitions may be added, and existing
1827 # partitions may be removed.
1828 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1829
Yifan Hong10c530d2018-12-27 17:34:18 -08001830 if is_dynamic:
1831 self.device = 'map_partition("%s")' % partition
1832 else:
1833 if OPTIONS.source_info_dict is None:
1834 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1835 else:
1836 _, device_path = GetTypeAndDevice("/" + partition,
1837 OPTIONS.source_info_dict)
1838 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001839
Tao Baod8d14be2016-02-04 14:26:02 -08001840 @property
1841 def required_cache(self):
1842 return self._required_cache
1843
Tao Bao76def242017-11-21 09:25:31 -08001844 def WriteScript(self, script, output_zip, progress=None,
1845 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001846 if not self.src:
1847 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001848 script.Print("Patching %s image unconditionally..." % (self.partition,))
1849 else:
1850 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001851
Dan Albert8b72aef2015-03-23 19:13:21 -07001852 if progress:
1853 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001854 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001855
1856 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001857 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001858
Tao Bao9bc6bb22015-11-09 16:58:28 -08001859 def WriteStrictVerifyScript(self, script):
1860 """Verify all the blocks in the care_map, including clobbered blocks.
1861
1862 This differs from the WriteVerifyScript() function: a) it prints different
1863 error messages; b) it doesn't allow half-way updated images to pass the
1864 verification."""
1865
1866 partition = self.partition
1867 script.Print("Verifying %s..." % (partition,))
1868 ranges = self.tgt.care_map
1869 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001870 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001871 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1872 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001873 self.device, ranges_str,
1874 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001875 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001876 script.AppendExtra("")
1877
Tao Baod522bdc2016-04-12 15:53:16 -07001878 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001879 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001880
1881 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001882 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001883 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001884
1885 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001886 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001887 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001888 ranges = self.touched_src_ranges
1889 expected_sha1 = self.touched_src_sha1
1890 else:
1891 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1892 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001893
1894 # No blocks to be checked, skipping.
1895 if not ranges:
1896 return
1897
Tao Bao5ece99d2015-05-12 11:42:31 -07001898 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001899 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001900 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001901 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1902 '"%s.patch.dat")) then' % (
1903 self.device, ranges_str, expected_sha1,
1904 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001905 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001906 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001907
Tianjie Xufc3422a2015-12-15 11:53:59 -08001908 if self.version >= 4:
1909
1910 # Bug: 21124327
1911 # When generating incrementals for the system and vendor partitions in
1912 # version 4 or newer, explicitly check the first block (which contains
1913 # the superblock) of the partition to see if it's what we expect. If
1914 # this check fails, give an explicit log message about the partition
1915 # having been remounted R/W (the most likely explanation).
1916 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08001917 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08001918
1919 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001920 if partition == "system":
1921 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1922 else:
1923 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001924 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08001925 'ifelse (block_image_recover({device}, "{ranges}") && '
1926 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001927 'package_extract_file("{partition}.transfer.list"), '
1928 '"{partition}.new.dat", "{partition}.patch.dat"), '
1929 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001930 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001931 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001932 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001933
Tao Baodd2a5892015-03-12 12:32:37 -07001934 # Abort the OTA update. Note that the incremental OTA cannot be applied
1935 # even if it may match the checksum of the target partition.
1936 # a) If version < 3, operations like move and erase will make changes
1937 # unconditionally and damage the partition.
1938 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001939 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001940 if partition == "system":
1941 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1942 else:
1943 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1944 script.AppendExtra((
1945 'abort("E%d: %s partition has unexpected contents");\n'
1946 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001947
Yifan Hong10c530d2018-12-27 17:34:18 -08001948 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07001949 partition = self.partition
1950 script.Print('Verifying the updated %s image...' % (partition,))
1951 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1952 ranges = self.tgt.care_map
1953 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001954 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001955 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001956 self.device, ranges_str,
1957 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001958
1959 # Bug: 20881595
1960 # Verify that extended blocks are really zeroed out.
1961 if self.tgt.extended:
1962 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001963 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001964 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001965 self.device, ranges_str,
1966 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001967 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001968 if partition == "system":
1969 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1970 else:
1971 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001972 script.AppendExtra(
1973 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001974 ' abort("E%d: %s partition has unexpected non-zero contents after '
1975 'OTA update");\n'
1976 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001977 else:
1978 script.Print('Verified the updated %s image.' % (partition,))
1979
Tianjie Xu209db462016-05-24 17:34:52 -07001980 if partition == "system":
1981 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1982 else:
1983 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1984
Tao Bao5fcaaef2015-06-01 13:40:49 -07001985 script.AppendExtra(
1986 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001987 ' abort("E%d: %s partition has unexpected contents after OTA '
1988 'update");\n'
1989 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001990
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001991 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001992 ZipWrite(output_zip,
1993 '{}.transfer.list'.format(self.path),
1994 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001995
Tao Bao76def242017-11-21 09:25:31 -08001996 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1997 # its size. Quailty 9 almost triples the compression time but doesn't
1998 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001999 # zip | brotli(quality 6) | brotli(quality 9)
2000 # compressed_size: 942M | 869M (~8% reduced) | 854M
2001 # compression_time: 75s | 265s | 719s
2002 # decompression_time: 15s | 25s | 25s
2003
2004 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002005 brotli_cmd = ['brotli', '--quality=6',
2006 '--output={}.new.dat.br'.format(self.path),
2007 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002008 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002009 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002010
2011 new_data_name = '{}.new.dat.br'.format(self.partition)
2012 ZipWrite(output_zip,
2013 '{}.new.dat.br'.format(self.path),
2014 new_data_name,
2015 compress_type=zipfile.ZIP_STORED)
2016 else:
2017 new_data_name = '{}.new.dat'.format(self.partition)
2018 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2019
Dan Albert8e0178d2015-01-27 15:53:15 -08002020 ZipWrite(output_zip,
2021 '{}.patch.dat'.format(self.path),
2022 '{}.patch.dat'.format(self.partition),
2023 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002024
Tianjie Xu209db462016-05-24 17:34:52 -07002025 if self.partition == "system":
2026 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2027 else:
2028 code = ErrorCode.VENDOR_UPDATE_FAILURE
2029
Yifan Hong10c530d2018-12-27 17:34:18 -08002030 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002031 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002032 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002033 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002034 device=self.device, partition=self.partition,
2035 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002036 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002037
Dan Albert8b72aef2015-03-23 19:13:21 -07002038 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002039 data = source.ReadRangeSet(ranges)
2040 ctx = sha1()
2041
2042 for p in data:
2043 ctx.update(p)
2044
2045 return ctx.hexdigest()
2046
Tao Baoe9b61912015-07-09 17:37:49 -07002047 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2048 """Return the hash value for all zero blocks."""
2049 zero_block = '\x00' * 4096
2050 ctx = sha1()
2051 for _ in range(num_blocks):
2052 ctx.update(zero_block)
2053
2054 return ctx.hexdigest()
2055
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002056
2057DataImage = blockimgdiff.DataImage
2058
Tao Bao76def242017-11-21 09:25:31 -08002059
Doug Zongker96a57e72010-09-26 14:57:41 -07002060# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002061PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002062 "ext4": "EMMC",
2063 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002064 "f2fs": "EMMC",
2065 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002066}
Doug Zongker96a57e72010-09-26 14:57:41 -07002067
Tao Bao76def242017-11-21 09:25:31 -08002068
Doug Zongker96a57e72010-09-26 14:57:41 -07002069def GetTypeAndDevice(mount_point, info):
2070 fstab = info["fstab"]
2071 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002072 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2073 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002074 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002075 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002076
2077
2078def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002079 """Parses and converts a PEM-encoded certificate into DER-encoded.
2080
2081 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2082
2083 Returns:
2084 The decoded certificate string.
2085 """
2086 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002087 save = False
2088 for line in data.split("\n"):
2089 if "--END CERTIFICATE--" in line:
2090 break
2091 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002092 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002093 if "--BEGIN CERTIFICATE--" in line:
2094 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002095 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002096 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002097
Tao Bao04e1f012018-02-04 12:13:35 -08002098
2099def ExtractPublicKey(cert):
2100 """Extracts the public key (PEM-encoded) from the given certificate file.
2101
2102 Args:
2103 cert: The certificate filename.
2104
2105 Returns:
2106 The public key string.
2107
2108 Raises:
2109 AssertionError: On non-zero return from 'openssl'.
2110 """
2111 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2112 # While openssl 1.1 writes the key into the given filename followed by '-out',
2113 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2114 # stdout instead.
2115 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2116 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2117 pubkey, stderrdata = proc.communicate()
2118 assert proc.returncode == 0, \
2119 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2120 return pubkey
2121
2122
Tao Bao2cc0ca12019-03-15 10:44:43 -07002123def ExtractAvbPublicKey(key):
2124 """Extracts the AVB public key from the given public or private key.
2125
2126 Args:
2127 key: The input key file, which should be PEM-encoded public or private key.
2128
2129 Returns:
2130 The path to the extracted AVB public key file.
2131 """
2132 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2133 RunAndCheckOutput(
2134 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2135 return output
2136
2137
Doug Zongker412c02f2014-02-13 10:58:24 -08002138def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2139 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002140 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002141
Tao Bao6d5d6232018-03-09 17:04:42 -08002142 Most of the space in the boot and recovery images is just the kernel, which is
2143 identical for the two, so the resulting patch should be efficient. Add it to
2144 the output zip, along with a shell script that is run from init.rc on first
2145 boot to actually do the patching and install the new recovery image.
2146
2147 Args:
2148 input_dir: The top-level input directory of the target-files.zip.
2149 output_sink: The callback function that writes the result.
2150 recovery_img: File object for the recovery image.
2151 boot_img: File objects for the boot image.
2152 info_dict: A dict returned by common.LoadInfoDict() on the input
2153 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002154 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002155 if info_dict is None:
2156 info_dict = OPTIONS.info_dict
2157
Tao Bao6d5d6232018-03-09 17:04:42 -08002158 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002159
Tao Baof2cffbd2015-07-22 12:33:18 -07002160 if full_recovery_image:
2161 output_sink("etc/recovery.img", recovery_img.data)
2162
2163 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002164 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002165 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002166 # With system-root-image, boot and recovery images will have mismatching
2167 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2168 # to handle such a case.
2169 if system_root_image:
2170 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002171 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002172 assert not os.path.exists(path)
2173 else:
2174 diff_program = ["imgdiff"]
2175 if os.path.exists(path):
2176 diff_program.append("-b")
2177 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002178 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002179 else:
2180 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002181
2182 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2183 _, _, patch = d.ComputePatch()
2184 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002185
Dan Albertebb19aa2015-03-27 19:11:53 -07002186 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002187 # The following GetTypeAndDevice()s need to use the path in the target
2188 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002189 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2190 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2191 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002192 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002193
Tao Baof2cffbd2015-07-22 12:33:18 -07002194 if full_recovery_image:
2195 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002196if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2197 applypatch \\
2198 --flash /system/etc/recovery.img \\
2199 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2200 log -t recovery "Installing new recovery image: succeeded" || \\
2201 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002202else
2203 log -t recovery "Recovery image already installed"
2204fi
2205""" % {'type': recovery_type,
2206 'device': recovery_device,
2207 'sha1': recovery_img.sha1,
2208 'size': recovery_img.size}
2209 else:
2210 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002211if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2212 applypatch %(bonus_args)s \\
2213 --patch /system/recovery-from-boot.p \\
2214 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2215 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2216 log -t recovery "Installing new recovery image: succeeded" || \\
2217 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002218else
2219 log -t recovery "Recovery image already installed"
2220fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002221""" % {'boot_size': boot_img.size,
2222 'boot_sha1': boot_img.sha1,
2223 'recovery_size': recovery_img.size,
2224 'recovery_sha1': recovery_img.sha1,
2225 'boot_type': boot_type,
2226 'boot_device': boot_device,
2227 'recovery_type': recovery_type,
2228 'recovery_device': recovery_device,
2229 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002230
2231 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002232 # in the L release.
2233 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002234
Tao Bao32fcdab2018-10-12 10:30:39 -07002235 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002236
2237 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002238
2239
2240class DynamicPartitionUpdate(object):
2241 def __init__(self, src_group=None, tgt_group=None, progress=None,
2242 block_difference=None):
2243 self.src_group = src_group
2244 self.tgt_group = tgt_group
2245 self.progress = progress
2246 self.block_difference = block_difference
2247
2248 @property
2249 def src_size(self):
2250 if not self.block_difference:
2251 return 0
2252 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2253
2254 @property
2255 def tgt_size(self):
2256 if not self.block_difference:
2257 return 0
2258 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2259
2260 @staticmethod
2261 def _GetSparseImageSize(img):
2262 if not img:
2263 return 0
2264 return img.blocksize * img.total_blocks
2265
2266
2267class DynamicGroupUpdate(object):
2268 def __init__(self, src_size=None, tgt_size=None):
2269 # None: group does not exist. 0: no size limits.
2270 self.src_size = src_size
2271 self.tgt_size = tgt_size
2272
2273
2274class DynamicPartitionsDifference(object):
2275 def __init__(self, info_dict, block_diffs, progress_dict=None,
2276 source_info_dict=None):
2277 if progress_dict is None:
2278 progress_dict = dict()
2279
2280 self._remove_all_before_apply = False
2281 if source_info_dict is None:
2282 self._remove_all_before_apply = True
2283 source_info_dict = dict()
2284
2285 block_diff_dict = {e.partition:e for e in block_diffs}
2286 assert len(block_diff_dict) == len(block_diffs), \
2287 "Duplicated BlockDifference object for {}".format(
2288 [partition for partition, count in
2289 collections.Counter(e.partition for e in block_diffs).items()
2290 if count > 1])
2291
Yifan Hong79997e52019-01-23 16:56:19 -08002292 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002293
2294 for p, block_diff in block_diff_dict.items():
2295 self._partition_updates[p] = DynamicPartitionUpdate()
2296 self._partition_updates[p].block_difference = block_diff
2297
2298 for p, progress in progress_dict.items():
2299 if p in self._partition_updates:
2300 self._partition_updates[p].progress = progress
2301
2302 tgt_groups = shlex.split(info_dict.get(
2303 "super_partition_groups", "").strip())
2304 src_groups = shlex.split(source_info_dict.get(
2305 "super_partition_groups", "").strip())
2306
2307 for g in tgt_groups:
2308 for p in shlex.split(info_dict.get(
2309 "super_%s_partition_list" % g, "").strip()):
2310 assert p in self._partition_updates, \
2311 "{} is in target super_{}_partition_list but no BlockDifference " \
2312 "object is provided.".format(p, g)
2313 self._partition_updates[p].tgt_group = g
2314
2315 for g in src_groups:
2316 for p in shlex.split(source_info_dict.get(
2317 "super_%s_partition_list" % g, "").strip()):
2318 assert p in self._partition_updates, \
2319 "{} is in source super_{}_partition_list but no BlockDifference " \
2320 "object is provided.".format(p, g)
2321 self._partition_updates[p].src_group = g
2322
Yifan Hong45433e42019-01-18 13:55:25 -08002323 target_dynamic_partitions = set(shlex.split(info_dict.get(
2324 "dynamic_partition_list", "").strip()))
2325 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2326 if u.tgt_size)
2327 assert block_diffs_with_target == target_dynamic_partitions, \
2328 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2329 list(target_dynamic_partitions), list(block_diffs_with_target))
2330
2331 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2332 "dynamic_partition_list", "").strip()))
2333 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2334 if u.src_size)
2335 assert block_diffs_with_source == source_dynamic_partitions, \
2336 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2337 list(source_dynamic_partitions), list(block_diffs_with_source))
2338
Yifan Hong10c530d2018-12-27 17:34:18 -08002339 if self._partition_updates:
2340 logger.info("Updating dynamic partitions %s",
2341 self._partition_updates.keys())
2342
Yifan Hong79997e52019-01-23 16:56:19 -08002343 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002344
2345 for g in tgt_groups:
2346 self._group_updates[g] = DynamicGroupUpdate()
2347 self._group_updates[g].tgt_size = int(info_dict.get(
2348 "super_%s_group_size" % g, "0").strip())
2349
2350 for g in src_groups:
2351 if g not in self._group_updates:
2352 self._group_updates[g] = DynamicGroupUpdate()
2353 self._group_updates[g].src_size = int(source_info_dict.get(
2354 "super_%s_group_size" % g, "0").strip())
2355
2356 self._Compute()
2357
2358 def WriteScript(self, script, output_zip, write_verify_script=False):
2359 script.Comment('--- Start patching dynamic partitions ---')
2360 for p, u in self._partition_updates.items():
2361 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2362 script.Comment('Patch partition %s' % p)
2363 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2364 write_verify_script=False)
2365
2366 op_list_path = MakeTempFile()
2367 with open(op_list_path, 'w') as f:
2368 for line in self._op_list:
2369 f.write('{}\n'.format(line))
2370
2371 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2372
2373 script.Comment('Update dynamic partition metadata')
2374 script.AppendExtra('assert(update_dynamic_partitions('
2375 'package_extract_file("dynamic_partitions_op_list")));')
2376
2377 if write_verify_script:
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 u.block_difference.WritePostInstallVerifyScript(script)
2381 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2382
2383 for p, u in self._partition_updates.items():
2384 if u.tgt_size and u.src_size <= u.tgt_size:
2385 script.Comment('Patch partition %s' % p)
2386 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2387 write_verify_script=write_verify_script)
2388 if write_verify_script:
2389 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2390
2391 script.Comment('--- End patching dynamic partitions ---')
2392
2393 def _Compute(self):
2394 self._op_list = list()
2395
2396 def append(line):
2397 self._op_list.append(line)
2398
2399 def comment(line):
2400 self._op_list.append("# %s" % line)
2401
2402 if self._remove_all_before_apply:
2403 comment('Remove all existing dynamic partitions and groups before '
2404 'applying full OTA')
2405 append('remove_all_groups')
2406
2407 for p, u in self._partition_updates.items():
2408 if u.src_group and not u.tgt_group:
2409 append('remove %s' % p)
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 %s to default' % (p, u.src_group))
2414 append('move %s default' % p)
2415
2416 for p, u in self._partition_updates.items():
2417 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2418 comment('Shrink partition %s from %d to %d' %
2419 (p, u.src_size, u.tgt_size))
2420 append('resize %s %s' % (p, u.tgt_size))
2421
2422 for g, u in self._group_updates.items():
2423 if u.src_size is not None and u.tgt_size is None:
2424 append('remove_group %s' % g)
2425 if (u.src_size is not None and u.tgt_size is not None and
2426 u.src_size > u.tgt_size):
2427 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2428 append('resize_group %s %d' % (g, u.tgt_size))
2429
2430 for g, u in self._group_updates.items():
2431 if u.src_size is None and u.tgt_size is not None:
2432 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2433 append('add_group %s %d' % (g, u.tgt_size))
2434 if (u.src_size is not None and u.tgt_size is not None and
2435 u.src_size < u.tgt_size):
2436 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2437 append('resize_group %s %d' % (g, u.tgt_size))
2438
2439 for p, u in self._partition_updates.items():
2440 if u.tgt_group and not u.src_group:
2441 comment('Add partition %s to group %s' % (p, u.tgt_group))
2442 append('add %s %s' % (p, u.tgt_group))
2443
2444 for p, u in self._partition_updates.items():
2445 if u.tgt_size and u.src_size < u.tgt_size:
2446 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2447 append('resize %s %d' % (p, u.tgt_size))
2448
2449 for p, u in self._partition_updates.items():
2450 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2451 comment('Move partition %s from default to %s' %
2452 (p, u.tgt_group))
2453 append('move %s %s' % (p, u.tgt_group))