blob: 9d67c497e1cf254a8146fe75038524112c4522da [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Yifan Hong10c530d2018-12-27 17:34:18 -080017import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070018import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070019import errno
Tao Bao0ff15de2019-03-20 11:26:06 -070020import fnmatch
Doug Zongkereef39442009-04-02 12:14:19 -070021import getopt
22import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010023import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070024import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070025import json
26import logging
27import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070028import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080029import platform
Doug Zongkereef39442009-04-02 12:14:19 -070030import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070031import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070032import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080033import string
Doug Zongkereef39442009-04-02 12:14:19 -070034import subprocess
35import sys
36import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070037import threading
38import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070039import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080040from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070041
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070042import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080043import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070044
Tao Bao32fcdab2018-10-12 10:30:39 -070045logger = logging.getLogger(__name__)
46
Tao Bao986ee862018-10-04 15:46:16 -070047
Dan Albert8b72aef2015-03-23 19:13:21 -070048class Options(object):
49 def __init__(self):
Pavel Salomatov32676552019-03-06 20:00:45 +030050 base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
51 if base_out_path is None:
52 base_search_path = "out"
53 else:
Tao Bao2cc0ca12019-03-15 10:44:43 -070054 base_search_path = os.path.join(base_out_path,
55 os.path.basename(os.getcwd()))
Pavel Salomatov32676552019-03-06 20:00:45 +030056
Dan Albert8b72aef2015-03-23 19:13:21 -070057 platform_search_path = {
Pavel Salomatov32676552019-03-06 20:00:45 +030058 "linux2": os.path.join(base_search_path, "host/linux-x86"),
59 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070060 }
Doug Zongker85448772014-09-09 14:59:20 -070061
Tao Bao76def242017-11-21 09:25:31 -080062 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070063 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080064 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070065 self.extra_signapk_args = []
66 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080067 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070068 self.public_key_suffix = ".x509.pem"
69 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070070 # use otatools built boot_signer by default
71 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070072 self.boot_signer_args = []
73 self.verity_signer_path = None
74 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070075 self.verbose = False
76 self.tempfiles = []
77 self.device_specific = None
78 self.extras = {}
79 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070080 self.source_info_dict = None
81 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070082 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070083 # Stash size cannot exceed cache_size * threshold.
84 self.cache_size = None
85 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070086
87
88OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070089
Tao Bao71197512018-10-11 14:08:45 -070090# The block size that's used across the releasetools scripts.
91BLOCK_SIZE = 4096
92
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080093# Values for "certificate" in apkcerts that mean special things.
94SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
95
Tao Bao5cc0abb2019-03-21 10:18:05 -070096# The partitions allowed to be signed by AVB (Android Verified Boot 2.0). Note
97# that system_other is not in the list because we don't want to include its
98# descriptor into vbmeta.img.
99AVB_PARTITIONS = ('boot', 'dtbo', 'odm', 'product', 'product_services',
100 'recovery', 'system', 'vendor')
Tao Bao9dd909e2017-11-14 11:27:32 -0800101
Tianjie Xu861f4132018-09-12 11:49:33 -0700102# Partitions that should have their care_map added to META/care_map.pb
103PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
104 'odm')
105
106
Tianjie Xu209db462016-05-24 17:34:52 -0700107class ErrorCode(object):
108 """Define error_codes for failures that happen during the actual
109 update package installation.
110
111 Error codes 0-999 are reserved for failures before the package
112 installation (i.e. low battery, package verification failure).
113 Detailed code in 'bootable/recovery/error_code.h' """
114
115 SYSTEM_VERIFICATION_FAILURE = 1000
116 SYSTEM_UPDATE_FAILURE = 1001
117 SYSTEM_UNEXPECTED_CONTENTS = 1002
118 SYSTEM_NONZERO_CONTENTS = 1003
119 SYSTEM_RECOVER_FAILURE = 1004
120 VENDOR_VERIFICATION_FAILURE = 2000
121 VENDOR_UPDATE_FAILURE = 2001
122 VENDOR_UNEXPECTED_CONTENTS = 2002
123 VENDOR_NONZERO_CONTENTS = 2003
124 VENDOR_RECOVER_FAILURE = 2004
125 OEM_PROP_MISMATCH = 3000
126 FINGERPRINT_MISMATCH = 3001
127 THUMBPRINT_MISMATCH = 3002
128 OLDER_BUILD = 3003
129 DEVICE_MISMATCH = 3004
130 BAD_PATCH_FILE = 3005
131 INSUFFICIENT_CACHE_SPACE = 3006
132 TUNE_PARTITION_FAILURE = 3007
133 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800134
Tao Bao80921982018-03-21 21:02:19 -0700135
Dan Albert8b72aef2015-03-23 19:13:21 -0700136class ExternalError(RuntimeError):
137 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700138
139
Tao Bao32fcdab2018-10-12 10:30:39 -0700140def InitLogging():
141 DEFAULT_LOGGING_CONFIG = {
142 'version': 1,
143 'disable_existing_loggers': False,
144 'formatters': {
145 'standard': {
146 'format':
147 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
148 'datefmt': '%Y-%m-%d %H:%M:%S',
149 },
150 },
151 'handlers': {
152 'default': {
153 'class': 'logging.StreamHandler',
154 'formatter': 'standard',
155 },
156 },
157 'loggers': {
158 '': {
159 'handlers': ['default'],
160 'level': 'WARNING',
161 'propagate': True,
162 }
163 }
164 }
165 env_config = os.getenv('LOGGING_CONFIG')
166 if env_config:
167 with open(env_config) as f:
168 config = json.load(f)
169 else:
170 config = DEFAULT_LOGGING_CONFIG
171
172 # Increase the logging level for verbose mode.
173 if OPTIONS.verbose:
174 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
175 config['loggers']['']['level'] = 'INFO'
176
177 logging.config.dictConfig(config)
178
179
Tao Bao39451582017-05-04 11:10:47 -0700180def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700181 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700182
Tao Bao73dd4f42018-10-04 16:25:33 -0700183 Args:
184 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700185 verbose: Whether the commands should be shown. Default to the global
186 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700187 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
188 stdin, etc. stdout and stderr will default to subprocess.PIPE and
189 subprocess.STDOUT respectively unless caller specifies any of them.
190
191 Returns:
192 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700193 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700194 if 'stdout' not in kwargs and 'stderr' not in kwargs:
195 kwargs['stdout'] = subprocess.PIPE
196 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700197 # Don't log any if caller explicitly says so.
198 if verbose != False:
199 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700200 return subprocess.Popen(args, **kwargs)
201
202
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800203def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800204 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800205
206 Args:
207 args: The command represented as a list of strings.
208 verbose: Whether the commands should be shown. Default to the global
209 verbosity if unspecified.
210 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
211 stdin, etc. stdout and stderr will default to subprocess.PIPE and
212 subprocess.STDOUT respectively unless caller specifies any of them.
213
Bill Peckham889b0c62019-02-21 18:53:37 -0800214 Raises:
215 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800216 """
217 proc = Run(args, verbose=verbose, **kwargs)
218 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800219
220 if proc.returncode != 0:
221 raise ExternalError(
222 "Failed to run command '{}' (exit code {})".format(
223 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800224
225
Tao Bao986ee862018-10-04 15:46:16 -0700226def RunAndCheckOutput(args, verbose=None, **kwargs):
227 """Runs the given command and returns the output.
228
229 Args:
230 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700231 verbose: Whether the commands should be shown. Default to the global
232 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700233 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
234 stdin, etc. stdout and stderr will default to subprocess.PIPE and
235 subprocess.STDOUT respectively unless caller specifies any of them.
236
237 Returns:
238 The output string.
239
240 Raises:
241 ExternalError: On non-zero exit from the command.
242 """
Tao Bao986ee862018-10-04 15:46:16 -0700243 proc = Run(args, verbose=verbose, **kwargs)
244 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700245 # Don't log any if caller explicitly says so.
246 if verbose != False:
247 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700248 if proc.returncode != 0:
249 raise ExternalError(
250 "Failed to run command '{}' (exit code {}):\n{}".format(
251 args, proc.returncode, output))
252 return output
253
254
Tao Baoc765cca2018-01-31 17:32:40 -0800255def RoundUpTo4K(value):
256 rounded_up = value + 4095
257 return rounded_up - (rounded_up % 4096)
258
259
Ying Wang7e6d4e42010-12-13 16:25:36 -0800260def CloseInheritedPipes():
261 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
262 before doing other work."""
263 if platform.system() != "Darwin":
264 return
265 for d in range(3, 1025):
266 try:
267 stat = os.fstat(d)
268 if stat is not None:
269 pipebit = stat[0] & 0x1000
270 if pipebit != 0:
271 os.close(d)
272 except OSError:
273 pass
274
275
Tao Bao410ad8b2018-08-24 12:08:38 -0700276def LoadInfoDict(input_file, repacking=False):
277 """Loads the key/value pairs from the given input target_files.
278
279 It reads `META/misc_info.txt` file in the target_files input, does sanity
280 checks and returns the parsed key/value pairs for to the given build. It's
281 usually called early when working on input target_files files, e.g. when
282 generating OTAs, or signing builds. Note that the function may be called
283 against an old target_files file (i.e. from past dessert releases). So the
284 property parsing needs to be backward compatible.
285
286 In a `META/misc_info.txt`, a few properties are stored as links to the files
287 in the PRODUCT_OUT directory. It works fine with the build system. However,
288 they are no longer available when (re)generating images from target_files zip.
289 When `repacking` is True, redirect these properties to the actual files in the
290 unzipped directory.
291
292 Args:
293 input_file: The input target_files file, which could be an open
294 zipfile.ZipFile instance, or a str for the dir that contains the files
295 unzipped from a target_files file.
296 repacking: Whether it's trying repack an target_files file after loading the
297 info dict (default: False). If so, it will rewrite a few loaded
298 properties (e.g. selinux_fc, root_dir) to point to the actual files in
299 target_files file. When doing repacking, `input_file` must be a dir.
300
301 Returns:
302 A dict that contains the parsed key/value pairs.
303
304 Raises:
305 AssertionError: On invalid input arguments.
306 ValueError: On malformed input values.
307 """
308 if repacking:
309 assert isinstance(input_file, str), \
310 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700311
Doug Zongkerc9253822014-02-04 12:17:58 -0800312 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700313 if isinstance(input_file, zipfile.ZipFile):
314 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800315 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700316 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800317 try:
318 with open(path) as f:
319 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700320 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800321 if e.errno == errno.ENOENT:
322 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800323
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700324 try:
Michael Runge6e836112014-04-15 17:40:21 -0700325 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700326 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700327 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700328
Tao Bao410ad8b2018-08-24 12:08:38 -0700329 if "recovery_api_version" not in d:
330 raise ValueError("Failed to find 'recovery_api_version'")
331 if "fstab_version" not in d:
332 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800333
Tao Bao410ad8b2018-08-24 12:08:38 -0700334 if repacking:
Daniel Norman72c626f2019-05-13 15:58:14 -0700335 # "selinux_fc" properties should point to the file_contexts files
336 # (file_contexts.bin) under META/.
337 for key in d:
338 if key.endswith("selinux_fc"):
339 fc_basename = os.path.basename(d[key])
340 fc_config = os.path.join(input_file, "META", fc_basename)
341 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700342
Daniel Norman72c626f2019-05-13 15:58:14 -0700343 d[key] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700344
Tom Cherryd14b8952018-08-09 14:26:00 -0700345 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700346 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700347 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700348 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700349
Tao Baof54216f2016-03-29 15:12:37 -0700350 # Redirect {system,vendor}_base_fs_file.
351 if "system_base_fs_file" in d:
352 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700353 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700354 if os.path.exists(system_base_fs_file):
355 d["system_base_fs_file"] = system_base_fs_file
356 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700357 logger.warning(
358 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700359 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700360
361 if "vendor_base_fs_file" in d:
362 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700363 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700364 if os.path.exists(vendor_base_fs_file):
365 d["vendor_base_fs_file"] = vendor_base_fs_file
366 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700367 logger.warning(
368 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700369 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700370
Doug Zongker37974732010-09-16 17:44:38 -0700371 def makeint(key):
372 if key in d:
373 d[key] = int(d[key], 0)
374
375 makeint("recovery_api_version")
376 makeint("blocksize")
377 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700378 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700379 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700380 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700381 makeint("recovery_size")
382 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800383 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700384
Tao Baoa57ab9f2018-08-24 12:08:38 -0700385 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
386 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
387 # cases, since it may load the info_dict from an old build (e.g. when
388 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800389 system_root_image = d.get("system_root_image") == "true"
390 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700391 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700392 if isinstance(input_file, zipfile.ZipFile):
393 if recovery_fstab_path not in input_file.namelist():
394 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
395 else:
396 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
397 if not os.path.exists(path):
398 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800399 d["fstab"] = LoadRecoveryFSTab(
400 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700401
Tao Bao76def242017-11-21 09:25:31 -0800402 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700403 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700404 if isinstance(input_file, zipfile.ZipFile):
405 if recovery_fstab_path not in input_file.namelist():
406 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
407 else:
408 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
409 if not os.path.exists(path):
410 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800411 d["fstab"] = LoadRecoveryFSTab(
412 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700413
Tianjie Xucfa86222016-03-07 16:31:19 -0800414 else:
415 d["fstab"] = None
416
Tianjie Xu861f4132018-09-12 11:49:33 -0700417 # Tries to load the build props for all partitions with care_map, including
418 # system and vendor.
419 for partition in PARTITIONS_WITH_CARE_MAP:
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800420 partition_prop = "{}.build.prop".format(partition)
421 d[partition_prop] = LoadBuildProp(
Tianjie Xu861f4132018-09-12 11:49:33 -0700422 read_helper, "{}/build.prop".format(partition.upper()))
Bowgo Tsai71a4d5c2019-05-17 23:21:48 +0800423 # Some partition might use /<partition>/etc/build.prop as the new path.
424 # TODO: try new path first when majority of them switch to the new path.
425 if not d[partition_prop]:
426 d[partition_prop] = LoadBuildProp(
427 read_helper, "{}/etc/build.prop".format(partition.upper()))
Tianjie Xu861f4132018-09-12 11:49:33 -0700428 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800429
430 # Set up the salt (based on fingerprint or thumbprint) that will be used when
431 # adding AVB footer.
432 if d.get("avb_enable") == "true":
433 fp = None
434 if "build.prop" in d:
435 build_prop = d["build.prop"]
436 if "ro.build.fingerprint" in build_prop:
437 fp = build_prop["ro.build.fingerprint"]
438 elif "ro.build.thumbprint" in build_prop:
439 fp = build_prop["ro.build.thumbprint"]
440 if fp:
441 d["avb_salt"] = sha256(fp).hexdigest()
442
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700443 return d
444
Tao Baod1de6f32017-03-01 16:38:48 -0800445
Tao Baobcd1d162017-08-26 13:10:26 -0700446def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700447 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700448 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700449 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700450 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700451 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700452 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700453
Tao Baod1de6f32017-03-01 16:38:48 -0800454
Michael Runge6e836112014-04-15 17:40:21 -0700455def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700456 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700457 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700458 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700459 if not line or line.startswith("#"):
460 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700461 if "=" in line:
462 name, value = line.split("=", 1)
463 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700464 return d
465
Tao Baod1de6f32017-03-01 16:38:48 -0800466
Tianjie Xucfa86222016-03-07 16:31:19 -0800467def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
468 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700469 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800470 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700471 self.mount_point = mount_point
472 self.fs_type = fs_type
473 self.device = device
474 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700475 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700476
477 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800478 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700479 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700480 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700481 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700482
Tao Baod1de6f32017-03-01 16:38:48 -0800483 assert fstab_version == 2
484
485 d = {}
486 for line in data.split("\n"):
487 line = line.strip()
488 if not line or line.startswith("#"):
489 continue
490
491 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
492 pieces = line.split()
493 if len(pieces) != 5:
494 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
495
496 # Ignore entries that are managed by vold.
497 options = pieces[4]
498 if "voldmanaged=" in options:
499 continue
500
501 # It's a good line, parse it.
502 length = 0
503 options = options.split(",")
504 for i in options:
505 if i.startswith("length="):
506 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800507 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800508 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700509 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800510
Tao Baod1de6f32017-03-01 16:38:48 -0800511 mount_flags = pieces[3]
512 # Honor the SELinux context if present.
513 context = None
514 for i in mount_flags.split(","):
515 if i.startswith("context="):
516 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800517
Tao Baod1de6f32017-03-01 16:38:48 -0800518 mount_point = pieces[1]
519 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
520 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800521
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700522 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700523 # system. Other areas assume system is always at "/system" so point /system
524 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700525 if system_root_image:
526 assert not d.has_key("/system") and d.has_key("/")
527 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700528 return d
529
530
Doug Zongker37974732010-09-16 17:44:38 -0700531def DumpInfoDict(d):
532 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700533 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700534
Dan Albert8b72aef2015-03-23 19:13:21 -0700535
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800536def AppendAVBSigningArgs(cmd, partition):
537 """Append signing arguments for avbtool."""
538 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
539 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
540 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
541 if key_path and algorithm:
542 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700543 avb_salt = OPTIONS.info_dict.get("avb_salt")
544 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700545 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700546 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800547
548
Tao Bao02a08592018-07-22 12:40:45 -0700549def GetAvbChainedPartitionArg(partition, info_dict, key=None):
550 """Constructs and returns the arg to build or verify a chained partition.
551
552 Args:
553 partition: The partition name.
554 info_dict: The info dict to look up the key info and rollback index
555 location.
556 key: The key to be used for building or verifying the partition. Defaults to
557 the key listed in info_dict.
558
559 Returns:
560 A string of form "partition:rollback_index_location:key" that can be used to
561 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700562 """
563 if key is None:
564 key = info_dict["avb_" + partition + "_key_path"]
Tao Bao2cc0ca12019-03-15 10:44:43 -0700565 pubkey_path = ExtractAvbPublicKey(key)
Tao Bao02a08592018-07-22 12:40:45 -0700566 rollback_index_location = info_dict[
567 "avb_" + partition + "_rollback_index_location"]
568 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
569
570
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700571def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800572 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700573 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700574
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700575 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800576 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
577 we are building a two-step special image (i.e. building a recovery image to
578 be loaded into /boot in two-step OTAs).
579
580 Return the image data, or None if sourcedir does not appear to contains files
581 for building the requested image.
582 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700583
584 def make_ramdisk():
585 ramdisk_img = tempfile.NamedTemporaryFile()
586
587 if os.access(fs_config_file, os.F_OK):
588 cmd = ["mkbootfs", "-f", fs_config_file,
589 os.path.join(sourcedir, "RAMDISK")]
590 else:
591 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
592 p1 = Run(cmd, stdout=subprocess.PIPE)
593 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
594
595 p2.wait()
596 p1.wait()
597 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
598 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
599
600 return ramdisk_img
601
602 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
603 return None
604
605 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700606 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700607
Doug Zongkerd5131602012-08-02 14:46:42 -0700608 if info_dict is None:
609 info_dict = OPTIONS.info_dict
610
Doug Zongkereef39442009-04-02 12:14:19 -0700611 img = tempfile.NamedTemporaryFile()
612
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700613 if has_ramdisk:
614 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700615
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800616 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
617 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
618
619 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700620
Benoit Fradina45a8682014-07-14 21:00:43 +0200621 fn = os.path.join(sourcedir, "second")
622 if os.access(fn, os.F_OK):
623 cmd.append("--second")
624 cmd.append(fn)
625
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800626 fn = os.path.join(sourcedir, "dtb")
627 if os.access(fn, os.F_OK):
628 cmd.append("--dtb")
629 cmd.append(fn)
630
Doug Zongker171f1cd2009-06-15 22:36:37 -0700631 fn = os.path.join(sourcedir, "cmdline")
632 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700633 cmd.append("--cmdline")
634 cmd.append(open(fn).read().rstrip("\n"))
635
636 fn = os.path.join(sourcedir, "base")
637 if os.access(fn, os.F_OK):
638 cmd.append("--base")
639 cmd.append(open(fn).read().rstrip("\n"))
640
Ying Wang4de6b5b2010-08-25 14:29:34 -0700641 fn = os.path.join(sourcedir, "pagesize")
642 if os.access(fn, os.F_OK):
643 cmd.append("--pagesize")
644 cmd.append(open(fn).read().rstrip("\n"))
645
Tao Bao76def242017-11-21 09:25:31 -0800646 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700647 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700648 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700649
Tao Bao76def242017-11-21 09:25:31 -0800650 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000651 if args and args.strip():
652 cmd.extend(shlex.split(args))
653
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700654 if has_ramdisk:
655 cmd.extend(["--ramdisk", ramdisk_img.name])
656
Tao Baod95e9fd2015-03-29 23:07:41 -0700657 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800658 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700659 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700660 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700661 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700662 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700663
Tao Baobf70c312017-07-11 17:27:55 -0700664 # "boot" or "recovery", without extension.
665 partition_name = os.path.basename(sourcedir).lower()
666
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800667 if partition_name == "recovery":
668 if info_dict.get("include_recovery_dtbo") == "true":
669 fn = os.path.join(sourcedir, "recovery_dtbo")
670 cmd.extend(["--recovery_dtbo", fn])
671 if info_dict.get("include_recovery_acpio") == "true":
672 fn = os.path.join(sourcedir, "recovery_acpio")
673 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700674
Tao Bao986ee862018-10-04 15:46:16 -0700675 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700676
Tao Bao76def242017-11-21 09:25:31 -0800677 if (info_dict.get("boot_signer") == "true" and
678 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800679 # Hard-code the path as "/boot" for two-step special recovery image (which
680 # will be loaded into /boot during the two-step OTA).
681 if two_step_image:
682 path = "/boot"
683 else:
Tao Baobf70c312017-07-11 17:27:55 -0700684 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700685 cmd = [OPTIONS.boot_signer_path]
686 cmd.extend(OPTIONS.boot_signer_args)
687 cmd.extend([path, img.name,
688 info_dict["verity_key"] + ".pk8",
689 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700690 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700691
Tao Baod95e9fd2015-03-29 23:07:41 -0700692 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800693 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700694 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700695 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800696 # We have switched from the prebuilt futility binary to using the tool
697 # (futility-host) built from the source. Override the setting in the old
698 # TF.zip.
699 futility = info_dict["futility"]
700 if futility.startswith("prebuilts/"):
701 futility = "futility-host"
702 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700703 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700704 info_dict["vboot_key"] + ".vbprivk",
705 info_dict["vboot_subkey"] + ".vbprivk",
706 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700707 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700708 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700709
Tao Baof3282b42015-04-01 11:21:55 -0700710 # Clean up the temp files.
711 img_unsigned.close()
712 img_keyblock.close()
713
David Zeuthen8fecb282017-12-01 16:24:01 -0500714 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800715 if info_dict.get("avb_enable") == "true":
Tao Baof88e0ce2019-03-18 14:01:38 -0700716 avbtool = info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500717 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400718 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700719 "--partition_size", str(part_size), "--partition_name",
720 partition_name]
721 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500722 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400723 if args and args.strip():
724 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700725 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500726
727 img.seek(os.SEEK_SET, 0)
728 data = img.read()
729
730 if has_ramdisk:
731 ramdisk_img.close()
732 img.close()
733
734 return data
735
736
Doug Zongkerd5131602012-08-02 14:46:42 -0700737def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800738 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700739 """Return a File object with the desired bootable image.
740
741 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
742 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
743 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700744
Doug Zongker55d93282011-01-25 17:03:34 -0800745 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
746 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700747 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800748 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700749
750 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
751 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700752 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700753 return File.FromLocalFile(name, prebuilt_path)
754
Tao Bao32fcdab2018-10-12 10:30:39 -0700755 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700756
757 if info_dict is None:
758 info_dict = OPTIONS.info_dict
759
760 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800761 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
762 # for recovery.
763 has_ramdisk = (info_dict.get("system_root_image") != "true" or
764 prebuilt_name != "boot.img" or
765 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700766
Doug Zongker6f1d0312014-08-22 08:07:12 -0700767 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400768 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
769 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800770 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700771 if data:
772 return File(name, data)
773 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800774
Doug Zongkereef39442009-04-02 12:14:19 -0700775
Narayan Kamatha07bf042017-08-14 14:49:21 +0100776def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800777 """Gunzips the given gzip compressed file to a given output file."""
778 with gzip.open(in_filename, "rb") as in_file, \
779 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100780 shutil.copyfileobj(in_file, out_file)
781
782
Tao Bao0ff15de2019-03-20 11:26:06 -0700783def UnzipToDir(filename, dirname, patterns=None):
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800784 """Unzips the archive to the given directory.
785
786 Args:
787 filename: The name of the zip file to unzip.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800788 dirname: Where the unziped files will land.
Tao Bao0ff15de2019-03-20 11:26:06 -0700789 patterns: Files to unzip from the archive. If omitted, will unzip the entire
790 archvie. Non-matching patterns will be filtered out. If there's no match
791 after the filtering, no file will be unzipped.
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800792 """
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800793 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
Tao Bao0ff15de2019-03-20 11:26:06 -0700794 if patterns is not None:
795 # Filter out non-matching patterns. unzip will complain otherwise.
796 with zipfile.ZipFile(filename) as input_zip:
797 names = input_zip.namelist()
798 filtered = [
799 pattern for pattern in patterns if fnmatch.filter(names, pattern)]
800
801 # There isn't any matching files. Don't unzip anything.
802 if not filtered:
803 return
804 cmd.extend(filtered)
805
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800806 RunAndCheckOutput(cmd)
807
808
Doug Zongker75f17362009-12-08 13:46:44 -0800809def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800810 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800811
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800812 Args:
813 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
814 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
815
816 pattern: Files to unzip from the archive. If omitted, will unzip the entire
817 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800818
Tao Bao1c830bf2017-12-25 10:43:47 -0800819 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800820 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800821 """
Doug Zongkereef39442009-04-02 12:14:19 -0700822
Tao Bao1c830bf2017-12-25 10:43:47 -0800823 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800824 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
825 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800826 UnzipToDir(m.group(1), tmp, pattern)
827 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800828 filename = m.group(1)
829 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800830 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800831
Tao Baodba59ee2018-01-09 13:21:02 -0800832 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700833
834
Yifan Hong8a66a712019-04-04 15:37:57 -0700835def GetUserImage(which, tmpdir, input_zip,
836 info_dict=None,
837 allow_shared_blocks=None,
838 hashtree_info_generator=None,
839 reset_file_map=False):
840 """Returns an Image object suitable for passing to BlockImageDiff.
841
842 This function loads the specified image from the given path. If the specified
843 image is sparse, it also performs additional processing for OTA purpose. For
844 example, it always adds block 0 to clobbered blocks list. It also detects
845 files that cannot be reconstructed from the block list, for whom we should
846 avoid applying imgdiff.
847
848 Args:
849 which: The partition name.
850 tmpdir: The directory that contains the prebuilt image and block map file.
851 input_zip: The target-files ZIP archive.
852 info_dict: The dict to be looked up for relevant info.
853 allow_shared_blocks: If image is sparse, whether having shared blocks is
854 allowed. If none, it is looked up from info_dict.
855 hashtree_info_generator: If present and image is sparse, generates the
856 hashtree_info for this sparse image.
857 reset_file_map: If true and image is sparse, reset file map before returning
858 the image.
859 Returns:
860 A Image object. If it is a sparse image and reset_file_map is False, the
861 image will have file_map info loaded.
862 """
863 if info_dict == None:
864 info_dict = LoadInfoDict(input_zip)
865
866 is_sparse = info_dict.get("extfs_sparse_flag")
867
868 # When target uses 'BOARD_EXT4_SHARE_DUP_BLOCKS := true', images may contain
869 # shared blocks (i.e. some blocks will show up in multiple files' block
870 # list). We can only allocate such shared blocks to the first "owner", and
871 # disable imgdiff for all later occurrences.
872 if allow_shared_blocks is None:
873 allow_shared_blocks = info_dict.get("ext4_share_dup_blocks") == "true"
874
875 if is_sparse:
876 img = GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
877 hashtree_info_generator)
878 if reset_file_map:
879 img.ResetFileMap()
880 return img
881 else:
882 return GetNonSparseImage(which, tmpdir, hashtree_info_generator)
883
884
885def GetNonSparseImage(which, tmpdir, hashtree_info_generator=None):
886 """Returns a Image object suitable for passing to BlockImageDiff.
887
888 This function loads the specified non-sparse image from the given path.
889
890 Args:
891 which: The partition name.
892 tmpdir: The directory that contains the prebuilt image and block map file.
893 Returns:
894 A Image object.
895 """
896 path = os.path.join(tmpdir, "IMAGES", which + ".img")
897 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
898
899 # The image and map files must have been created prior to calling
900 # ota_from_target_files.py (since LMP).
901 assert os.path.exists(path) and os.path.exists(mappath)
902
903 return blockimgdiff.FileImage(path, hashtree_info_generator=
904 hashtree_info_generator)
905
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700906def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
907 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800908 """Returns a SparseImage object suitable for passing to BlockImageDiff.
909
910 This function loads the specified sparse image from the given path, and
911 performs additional processing for OTA purpose. For example, it always adds
912 block 0 to clobbered blocks list. It also detects files that cannot be
913 reconstructed from the block list, for whom we should avoid applying imgdiff.
914
915 Args:
Tao Baob2de7d92019-04-10 10:01:47 -0700916 which: The partition name, e.g. "system", "vendor".
Tao Baoc765cca2018-01-31 17:32:40 -0800917 tmpdir: The directory that contains the prebuilt image and block map file.
918 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800919 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700920 hashtree_info_generator: If present, generates the hashtree_info for this
921 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800922 Returns:
923 A SparseImage object, with file_map info loaded.
924 """
Tao Baoc765cca2018-01-31 17:32:40 -0800925 path = os.path.join(tmpdir, "IMAGES", which + ".img")
926 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
927
928 # The image and map files must have been created prior to calling
929 # ota_from_target_files.py (since LMP).
930 assert os.path.exists(path) and os.path.exists(mappath)
931
932 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
933 # it to clobbered_blocks so that it will be written to the target
934 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
935 clobbered_blocks = "0"
936
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700937 image = sparse_img.SparseImage(
938 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
939 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800940
941 # block.map may contain less blocks, because mke2fs may skip allocating blocks
942 # if they contain all zeros. We can't reconstruct such a file from its block
943 # list. Tag such entries accordingly. (Bug: 65213616)
944 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800945 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700946 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800947 continue
948
Tom Cherryd14b8952018-08-09 14:26:00 -0700949 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
950 # filename listed in system.map may contain an additional leading slash
951 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
952 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700953 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
954
Tom Cherryd14b8952018-08-09 14:26:00 -0700955 # Special handling another case, where files not under /system
956 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700957 if which == 'system' and not arcname.startswith('SYSTEM'):
958 arcname = 'ROOT/' + arcname
959
960 assert arcname in input_zip.namelist(), \
961 "Failed to find the ZIP entry for {}".format(entry)
962
Tao Baoc765cca2018-01-31 17:32:40 -0800963 info = input_zip.getinfo(arcname)
964 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800965
966 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800967 # image, check the original block list to determine its completeness. Note
968 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800969 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800970 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800971
Tao Baoc765cca2018-01-31 17:32:40 -0800972 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
973 ranges.extra['incomplete'] = True
974
975 return image
976
977
Doug Zongkereef39442009-04-02 12:14:19 -0700978def GetKeyPasswords(keylist):
979 """Given a list of keys, prompt the user to enter passwords for
980 those which require them. Return a {key: password} dict. password
981 will be None if the key has no password."""
982
Doug Zongker8ce7c252009-05-22 13:34:54 -0700983 no_passwords = []
984 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700985 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700986 devnull = open("/dev/null", "w+b")
987 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800988 # We don't need a password for things that aren't really keys.
989 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700990 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700991 continue
992
T.R. Fullhart37e10522013-03-18 10:31:26 -0700993 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700994 "-inform", "DER", "-nocrypt"],
995 stdin=devnull.fileno(),
996 stdout=devnull.fileno(),
997 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700998 p.communicate()
999 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001000 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -07001001 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001002 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -07001003 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
1004 "-inform", "DER", "-passin", "pass:"],
1005 stdin=devnull.fileno(),
1006 stdout=devnull.fileno(),
1007 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -07001008 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -07001009 if p.returncode == 0:
1010 # Encrypted key with empty string as password.
1011 key_passwords[k] = ''
1012 elif stderr.startswith('Error decrypting key'):
1013 # Definitely encrypted key.
1014 # It would have said "Error reading key" if it didn't parse correctly.
1015 need_passwords.append(k)
1016 else:
1017 # Potentially, a type of key that openssl doesn't understand.
1018 # We'll let the routines in signapk.jar handle it.
1019 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -07001020 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -07001021
T.R. Fullhart37e10522013-03-18 10:31:26 -07001022 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -08001023 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -07001024 return key_passwords
1025
1026
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001027def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -07001028 """Gets the minSdkVersion declared in the APK.
1029
1030 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
1031 This can be both a decimal number (API Level) or a codename.
1032
1033 Args:
1034 apk_name: The APK filename.
1035
1036 Returns:
1037 The parsed SDK version string.
1038
1039 Raises:
1040 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001041 """
Tao Baof47bf0f2018-03-21 23:28:51 -07001042 proc = Run(
1043 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
1044 stderr=subprocess.PIPE)
1045 stdoutdata, stderrdata = proc.communicate()
1046 if proc.returncode != 0:
1047 raise ExternalError(
1048 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
1049 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001050
Tao Baof47bf0f2018-03-21 23:28:51 -07001051 for line in stdoutdata.split("\n"):
1052 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001053 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
1054 if m:
1055 return m.group(1)
1056 raise ExternalError("No minSdkVersion returned by aapt")
1057
1058
1059def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -07001060 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001061
Tao Baof47bf0f2018-03-21 23:28:51 -07001062 If minSdkVersion is set to a codename, it is translated to a number using the
1063 provided map.
1064
1065 Args:
1066 apk_name: The APK filename.
1067
1068 Returns:
1069 The parsed SDK version number.
1070
1071 Raises:
1072 ExternalError: On failing to get the min SDK version number.
1073 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001074 version = GetMinSdkVersion(apk_name)
1075 try:
1076 return int(version)
1077 except ValueError:
1078 # Not a decimal number. Codename?
1079 if version in codename_to_api_level_map:
1080 return codename_to_api_level_map[version]
1081 else:
Tao Baof47bf0f2018-03-21 23:28:51 -07001082 raise ExternalError(
1083 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1084 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001085
1086
1087def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Baoffc9a302019-03-22 23:16:58 -07001088 codename_to_api_level_map=None, whole_file=False,
1089 extra_signapk_args=None):
Doug Zongkereef39442009-04-02 12:14:19 -07001090 """Sign the input_name zip/jar/apk, producing output_name. Use the
1091 given key and password (the latter may be None if the key does not
1092 have a password.
1093
Doug Zongker951495f2009-08-14 12:44:19 -07001094 If whole_file is true, use the "-w" option to SignApk to embed a
1095 signature that covers the whole file in the archive comment of the
1096 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001097
1098 min_api_level is the API Level (int) of the oldest platform this file may end
1099 up on. If not specified for an APK, the API Level is obtained by interpreting
1100 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1101
1102 codename_to_api_level_map is needed to translate the codename which may be
1103 encountered as the APK's minSdkVersion.
Tao Baoffc9a302019-03-22 23:16:58 -07001104
1105 Caller may optionally specify extra args to be passed to SignApk, which
1106 defaults to OPTIONS.extra_signapk_args if omitted.
Doug Zongkereef39442009-04-02 12:14:19 -07001107 """
Tao Bao76def242017-11-21 09:25:31 -08001108 if codename_to_api_level_map is None:
1109 codename_to_api_level_map = {}
Tao Baoffc9a302019-03-22 23:16:58 -07001110 if extra_signapk_args is None:
1111 extra_signapk_args = OPTIONS.extra_signapk_args
Doug Zongker951495f2009-08-14 12:44:19 -07001112
Alex Klyubin9667b182015-12-10 13:38:50 -08001113 java_library_path = os.path.join(
1114 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1115
Tao Baoe95540e2016-11-08 12:08:53 -08001116 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1117 ["-Djava.library.path=" + java_library_path,
1118 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
Tao Baoffc9a302019-03-22 23:16:58 -07001119 extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001120 if whole_file:
1121 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001122
1123 min_sdk_version = min_api_level
1124 if min_sdk_version is None:
1125 if not whole_file:
1126 min_sdk_version = GetMinSdkVersionInt(
1127 input_name, codename_to_api_level_map)
1128 if min_sdk_version is not None:
1129 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1130
T.R. Fullhart37e10522013-03-18 10:31:26 -07001131 cmd.extend([key + OPTIONS.public_key_suffix,
1132 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001133 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001134
Tao Bao73dd4f42018-10-04 16:25:33 -07001135 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001136 if password is not None:
1137 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001138 stdoutdata, _ = proc.communicate(password)
1139 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001140 raise ExternalError(
1141 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001142 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001143
Doug Zongkereef39442009-04-02 12:14:19 -07001144
Doug Zongker37974732010-09-16 17:44:38 -07001145def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001146 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001147
Tao Bao9dd909e2017-11-14 11:27:32 -08001148 For non-AVB images, raise exception if the data is too big. Print a warning
1149 if the data is nearing the maximum size.
1150
1151 For AVB images, the actual image size should be identical to the limit.
1152
1153 Args:
1154 data: A string that contains all the data for the partition.
1155 target: The partition name. The ".img" suffix is optional.
1156 info_dict: The dict to be looked up for relevant info.
1157 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001158 if target.endswith(".img"):
1159 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001160 mount_point = "/" + target
1161
Ying Wangf8824af2014-06-03 14:07:27 -07001162 fs_type = None
1163 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001164 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001165 if mount_point == "/userdata":
1166 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001167 p = info_dict["fstab"][mount_point]
1168 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001169 device = p.device
1170 if "/" in device:
1171 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001172 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001173 if not fs_type or not limit:
1174 return
Doug Zongkereef39442009-04-02 12:14:19 -07001175
Andrew Boie0f9aec82012-02-14 09:32:52 -08001176 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001177 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1178 # path.
1179 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1180 if size != limit:
1181 raise ExternalError(
1182 "Mismatching image size for %s: expected %d actual %d" % (
1183 target, limit, size))
1184 else:
1185 pct = float(size) * 100.0 / limit
1186 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1187 if pct >= 99.0:
1188 raise ExternalError(msg)
1189 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001190 logger.warning("\n WARNING: %s\n", msg)
1191 else:
1192 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001193
1194
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001195def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001196 """Parses the APK certs info from a given target-files zip.
1197
1198 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1199 tuple with the following elements: (1) a dictionary that maps packages to
1200 certs (based on the "certificate" and "private_key" attributes in the file;
1201 (2) a string representing the extension of compressed APKs in the target files
1202 (e.g ".gz", ".bro").
1203
1204 Args:
1205 tf_zip: The input target_files ZipFile (already open).
1206
1207 Returns:
1208 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1209 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1210 no compressed APKs.
1211 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001212 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001213 compressed_extension = None
1214
Tao Bao0f990332017-09-08 19:02:54 -07001215 # META/apkcerts.txt contains the info for _all_ the packages known at build
1216 # time. Filter out the ones that are not installed.
1217 installed_files = set()
1218 for name in tf_zip.namelist():
1219 basename = os.path.basename(name)
1220 if basename:
1221 installed_files.add(basename)
1222
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001223 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1224 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001225 if not line:
1226 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001227 m = re.match(
1228 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1229 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1230 line)
1231 if not m:
1232 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001233
Tao Bao818ddf52018-01-05 11:17:34 -08001234 matches = m.groupdict()
1235 cert = matches["CERT"]
1236 privkey = matches["PRIVKEY"]
1237 name = matches["NAME"]
1238 this_compressed_extension = matches["COMPRESSED"]
1239
1240 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1241 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1242 if cert in SPECIAL_CERT_STRINGS and not privkey:
1243 certmap[name] = cert
1244 elif (cert.endswith(OPTIONS.public_key_suffix) and
1245 privkey.endswith(OPTIONS.private_key_suffix) and
1246 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1247 certmap[name] = cert[:-public_key_suffix_len]
1248 else:
1249 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1250
1251 if not this_compressed_extension:
1252 continue
1253
1254 # Only count the installed files.
1255 filename = name + '.' + this_compressed_extension
1256 if filename not in installed_files:
1257 continue
1258
1259 # Make sure that all the values in the compression map have the same
1260 # extension. We don't support multiple compression methods in the same
1261 # system image.
1262 if compressed_extension:
1263 if this_compressed_extension != compressed_extension:
1264 raise ValueError(
1265 "Multiple compressed extensions: {} vs {}".format(
1266 compressed_extension, this_compressed_extension))
1267 else:
1268 compressed_extension = this_compressed_extension
1269
1270 return (certmap,
1271 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001272
1273
Doug Zongkereef39442009-04-02 12:14:19 -07001274COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001275Global options
1276
1277 -p (--path) <dir>
1278 Prepend <dir>/bin to the list of places to search for binaries run by this
1279 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001280
Doug Zongker05d3dea2009-06-22 11:32:31 -07001281 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001282 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001283
Tao Bao30df8b42018-04-23 15:32:53 -07001284 -x (--extra) <key=value>
1285 Add a key/value pair to the 'extras' dict, which device-specific extension
1286 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001287
Doug Zongkereef39442009-04-02 12:14:19 -07001288 -v (--verbose)
1289 Show command lines being executed.
1290
1291 -h (--help)
1292 Display this usage message and exit.
1293"""
1294
1295def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001296 print(docstring.rstrip("\n"))
1297 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001298
1299
1300def ParseOptions(argv,
1301 docstring,
1302 extra_opts="", extra_long_opts=(),
1303 extra_option_handler=None):
1304 """Parse the options in argv and return any arguments that aren't
1305 flags. docstring is the calling module's docstring, to be displayed
1306 for errors and -h. extra_opts and extra_long_opts are for flags
1307 defined by the caller, which are processed by passing them to
1308 extra_option_handler."""
1309
1310 try:
1311 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001312 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001313 ["help", "verbose", "path=", "signapk_path=",
1314 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001315 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001316 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1317 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001318 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001319 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001320 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001321 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001322 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001323 sys.exit(2)
1324
Doug Zongkereef39442009-04-02 12:14:19 -07001325 for o, a in opts:
1326 if o in ("-h", "--help"):
1327 Usage(docstring)
1328 sys.exit()
1329 elif o in ("-v", "--verbose"):
1330 OPTIONS.verbose = True
1331 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001332 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001333 elif o in ("--signapk_path",):
1334 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001335 elif o in ("--signapk_shared_library_path",):
1336 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001337 elif o in ("--extra_signapk_args",):
1338 OPTIONS.extra_signapk_args = shlex.split(a)
1339 elif o in ("--java_path",):
1340 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001341 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001342 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001343 elif o in ("--public_key_suffix",):
1344 OPTIONS.public_key_suffix = a
1345 elif o in ("--private_key_suffix",):
1346 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001347 elif o in ("--boot_signer_path",):
1348 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001349 elif o in ("--boot_signer_args",):
1350 OPTIONS.boot_signer_args = shlex.split(a)
1351 elif o in ("--verity_signer_path",):
1352 OPTIONS.verity_signer_path = a
1353 elif o in ("--verity_signer_args",):
1354 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001355 elif o in ("-s", "--device_specific"):
1356 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001357 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001358 key, value = a.split("=", 1)
1359 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001360 else:
1361 if extra_option_handler is None or not extra_option_handler(o, a):
1362 assert False, "unknown option \"%s\"" % (o,)
1363
Doug Zongker85448772014-09-09 14:59:20 -07001364 if OPTIONS.search_path:
1365 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1366 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001367
1368 return args
1369
1370
Tao Bao4c851b12016-09-19 13:54:38 -07001371def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001372 """Make a temp file and add it to the list of things to be deleted
1373 when Cleanup() is called. Return the filename."""
1374 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1375 os.close(fd)
1376 OPTIONS.tempfiles.append(fn)
1377 return fn
1378
1379
Tao Bao1c830bf2017-12-25 10:43:47 -08001380def MakeTempDir(prefix='tmp', suffix=''):
1381 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1382
1383 Returns:
1384 The absolute pathname of the new directory.
1385 """
1386 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1387 OPTIONS.tempfiles.append(dir_name)
1388 return dir_name
1389
1390
Doug Zongkereef39442009-04-02 12:14:19 -07001391def Cleanup():
1392 for i in OPTIONS.tempfiles:
1393 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001394 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001395 else:
1396 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001397 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001398
1399
1400class PasswordManager(object):
1401 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001402 self.editor = os.getenv("EDITOR")
1403 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001404
1405 def GetPasswords(self, items):
1406 """Get passwords corresponding to each string in 'items',
1407 returning a dict. (The dict may have keys in addition to the
1408 values in 'items'.)
1409
1410 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1411 user edit that file to add more needed passwords. If no editor is
1412 available, or $ANDROID_PW_FILE isn't define, prompts the user
1413 interactively in the ordinary way.
1414 """
1415
1416 current = self.ReadFile()
1417
1418 first = True
1419 while True:
1420 missing = []
1421 for i in items:
1422 if i not in current or not current[i]:
1423 missing.append(i)
1424 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001425 if not missing:
1426 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001427
1428 for i in missing:
1429 current[i] = ""
1430
1431 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001432 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001433 answer = raw_input("try to edit again? [y]> ").strip()
1434 if answer and answer[0] not in 'yY':
1435 raise RuntimeError("key passwords unavailable")
1436 first = False
1437
1438 current = self.UpdateAndReadFile(current)
1439
Dan Albert8b72aef2015-03-23 19:13:21 -07001440 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001441 """Prompt the user to enter a value (password) for each key in
1442 'current' whose value is fales. Returns a new dict with all the
1443 values.
1444 """
1445 result = {}
1446 for k, v in sorted(current.iteritems()):
1447 if v:
1448 result[k] = v
1449 else:
1450 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001451 result[k] = getpass.getpass(
1452 "Enter password for %s key> " % k).strip()
1453 if result[k]:
1454 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001455 return result
1456
1457 def UpdateAndReadFile(self, current):
1458 if not self.editor or not self.pwfile:
1459 return self.PromptResult(current)
1460
1461 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001462 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001463 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1464 f.write("# (Additional spaces are harmless.)\n\n")
1465
1466 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001467 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1468 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001469 f.write("[[[ %s ]]] %s\n" % (v, k))
1470 if not v and first_line is None:
1471 # position cursor on first line with no password.
1472 first_line = i + 4
1473 f.close()
1474
Tao Bao986ee862018-10-04 15:46:16 -07001475 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001476
1477 return self.ReadFile()
1478
1479 def ReadFile(self):
1480 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001481 if self.pwfile is None:
1482 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001483 try:
1484 f = open(self.pwfile, "r")
1485 for line in f:
1486 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001487 if not line or line[0] == '#':
1488 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001489 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1490 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001491 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001492 else:
1493 result[m.group(2)] = m.group(1)
1494 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001495 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001496 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001497 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001498 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001499
1500
Dan Albert8e0178d2015-01-27 15:53:15 -08001501def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1502 compress_type=None):
1503 import datetime
1504
1505 # http://b/18015246
1506 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1507 # for files larger than 2GiB. We can work around this by adjusting their
1508 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1509 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1510 # it isn't clear to me exactly what circumstances cause this).
1511 # `zipfile.write()` must be used directly to work around this.
1512 #
1513 # This mess can be avoided if we port to python3.
1514 saved_zip64_limit = zipfile.ZIP64_LIMIT
1515 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1516
1517 if compress_type is None:
1518 compress_type = zip_file.compression
1519 if arcname is None:
1520 arcname = filename
1521
1522 saved_stat = os.stat(filename)
1523
1524 try:
1525 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1526 # file to be zipped and reset it when we're done.
1527 os.chmod(filename, perms)
1528
1529 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001530 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1531 # intentional. zip stores datetimes in local time without a time zone
1532 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1533 # in the zip archive.
1534 local_epoch = datetime.datetime.fromtimestamp(0)
1535 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001536 os.utime(filename, (timestamp, timestamp))
1537
1538 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1539 finally:
1540 os.chmod(filename, saved_stat.st_mode)
1541 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1542 zipfile.ZIP64_LIMIT = saved_zip64_limit
1543
1544
Tao Bao58c1b962015-05-20 09:32:18 -07001545def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001546 compress_type=None):
1547 """Wrap zipfile.writestr() function to work around the zip64 limit.
1548
1549 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1550 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1551 when calling crc32(bytes).
1552
1553 But it still works fine to write a shorter string into a large zip file.
1554 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1555 when we know the string won't be too long.
1556 """
1557
1558 saved_zip64_limit = zipfile.ZIP64_LIMIT
1559 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1560
1561 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1562 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001563 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001564 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001565 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001566 else:
Tao Baof3282b42015-04-01 11:21:55 -07001567 zinfo = zinfo_or_arcname
1568
1569 # If compress_type is given, it overrides the value in zinfo.
1570 if compress_type is not None:
1571 zinfo.compress_type = compress_type
1572
Tao Bao58c1b962015-05-20 09:32:18 -07001573 # If perms is given, it has a priority.
1574 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001575 # If perms doesn't set the file type, mark it as a regular file.
1576 if perms & 0o770000 == 0:
1577 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001578 zinfo.external_attr = perms << 16
1579
Tao Baof3282b42015-04-01 11:21:55 -07001580 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001581 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1582
Dan Albert8b72aef2015-03-23 19:13:21 -07001583 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001584 zipfile.ZIP64_LIMIT = saved_zip64_limit
1585
1586
Tao Bao89d7ab22017-12-14 17:05:33 -08001587def ZipDelete(zip_filename, entries):
1588 """Deletes entries from a ZIP file.
1589
1590 Since deleting entries from a ZIP file is not supported, it shells out to
1591 'zip -d'.
1592
1593 Args:
1594 zip_filename: The name of the ZIP file.
1595 entries: The name of the entry, or the list of names to be deleted.
1596
1597 Raises:
1598 AssertionError: In case of non-zero return from 'zip'.
1599 """
1600 if isinstance(entries, basestring):
1601 entries = [entries]
1602 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001603 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001604
1605
Tao Baof3282b42015-04-01 11:21:55 -07001606def ZipClose(zip_file):
1607 # http://b/18015246
1608 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1609 # central directory.
1610 saved_zip64_limit = zipfile.ZIP64_LIMIT
1611 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1612
1613 zip_file.close()
1614
1615 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001616
1617
1618class DeviceSpecificParams(object):
1619 module = None
1620 def __init__(self, **kwargs):
1621 """Keyword arguments to the constructor become attributes of this
1622 object, which is passed to all functions in the device-specific
1623 module."""
1624 for k, v in kwargs.iteritems():
1625 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001626 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001627
1628 if self.module is None:
1629 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001630 if not path:
1631 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001632 try:
1633 if os.path.isdir(path):
1634 info = imp.find_module("releasetools", [path])
1635 else:
1636 d, f = os.path.split(path)
1637 b, x = os.path.splitext(f)
1638 if x == ".py":
1639 f = b
1640 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001641 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001642 self.module = imp.load_module("device_specific", *info)
1643 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001644 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001645
1646 def _DoCall(self, function_name, *args, **kwargs):
1647 """Call the named function in the device-specific module, passing
1648 the given args and kwargs. The first argument to the call will be
1649 the DeviceSpecific object itself. If there is no module, or the
1650 module does not define the function, return the value of the
1651 'default' kwarg (which itself defaults to None)."""
1652 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001653 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001654 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1655
1656 def FullOTA_Assertions(self):
1657 """Called after emitting the block of assertions at the top of a
1658 full OTA package. Implementations can add whatever additional
1659 assertions they like."""
1660 return self._DoCall("FullOTA_Assertions")
1661
Doug Zongkere5ff5902012-01-17 10:55:37 -08001662 def FullOTA_InstallBegin(self):
1663 """Called at the start of full OTA installation."""
1664 return self._DoCall("FullOTA_InstallBegin")
1665
Yifan Hong10c530d2018-12-27 17:34:18 -08001666 def FullOTA_GetBlockDifferences(self):
1667 """Called during full OTA installation and verification.
1668 Implementation should return a list of BlockDifference objects describing
1669 the update on each additional partitions.
1670 """
1671 return self._DoCall("FullOTA_GetBlockDifferences")
1672
Doug Zongker05d3dea2009-06-22 11:32:31 -07001673 def FullOTA_InstallEnd(self):
1674 """Called at the end of full OTA installation; typically this is
1675 used to install the image for the device's baseband processor."""
1676 return self._DoCall("FullOTA_InstallEnd")
1677
1678 def IncrementalOTA_Assertions(self):
1679 """Called after emitting the block of assertions at the top of an
1680 incremental OTA package. Implementations can add whatever
1681 additional assertions they like."""
1682 return self._DoCall("IncrementalOTA_Assertions")
1683
Doug Zongkere5ff5902012-01-17 10:55:37 -08001684 def IncrementalOTA_VerifyBegin(self):
1685 """Called at the start of the verification phase of incremental
1686 OTA installation; additional checks can be placed here to abort
1687 the script before any changes are made."""
1688 return self._DoCall("IncrementalOTA_VerifyBegin")
1689
Doug Zongker05d3dea2009-06-22 11:32:31 -07001690 def IncrementalOTA_VerifyEnd(self):
1691 """Called at the end of the verification phase of incremental OTA
1692 installation; additional checks can be placed here to abort the
1693 script before any changes are made."""
1694 return self._DoCall("IncrementalOTA_VerifyEnd")
1695
Doug Zongkere5ff5902012-01-17 10:55:37 -08001696 def IncrementalOTA_InstallBegin(self):
1697 """Called at the start of incremental OTA installation (after
1698 verification is complete)."""
1699 return self._DoCall("IncrementalOTA_InstallBegin")
1700
Yifan Hong10c530d2018-12-27 17:34:18 -08001701 def IncrementalOTA_GetBlockDifferences(self):
1702 """Called during incremental OTA installation and verification.
1703 Implementation should return a list of BlockDifference objects describing
1704 the update on each additional partitions.
1705 """
1706 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1707
Doug Zongker05d3dea2009-06-22 11:32:31 -07001708 def IncrementalOTA_InstallEnd(self):
1709 """Called at the end of incremental OTA installation; typically
1710 this is used to install the image for the device's baseband
1711 processor."""
1712 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001713
Tao Bao9bc6bb22015-11-09 16:58:28 -08001714 def VerifyOTA_Assertions(self):
1715 return self._DoCall("VerifyOTA_Assertions")
1716
Tao Bao76def242017-11-21 09:25:31 -08001717
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001718class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001719 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001720 self.name = name
1721 self.data = data
1722 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001723 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001724 self.sha1 = sha1(data).hexdigest()
1725
1726 @classmethod
1727 def FromLocalFile(cls, name, diskname):
1728 f = open(diskname, "rb")
1729 data = f.read()
1730 f.close()
1731 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001732
1733 def WriteToTemp(self):
1734 t = tempfile.NamedTemporaryFile()
1735 t.write(self.data)
1736 t.flush()
1737 return t
1738
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001739 def WriteToDir(self, d):
1740 with open(os.path.join(d, self.name), "wb") as fp:
1741 fp.write(self.data)
1742
Geremy Condra36bd3652014-02-06 19:45:10 -08001743 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001744 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001745
Tao Bao76def242017-11-21 09:25:31 -08001746
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001747DIFF_PROGRAM_BY_EXT = {
1748 ".gz" : "imgdiff",
1749 ".zip" : ["imgdiff", "-z"],
1750 ".jar" : ["imgdiff", "-z"],
1751 ".apk" : ["imgdiff", "-z"],
1752 ".img" : "imgdiff",
1753 }
1754
Tao Bao76def242017-11-21 09:25:31 -08001755
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001756class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001757 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001758 self.tf = tf
1759 self.sf = sf
1760 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001761 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001762
1763 def ComputePatch(self):
1764 """Compute the patch (as a string of data) needed to turn sf into
1765 tf. Returns the same tuple as GetPatch()."""
1766
1767 tf = self.tf
1768 sf = self.sf
1769
Doug Zongker24cd2802012-08-14 16:36:15 -07001770 if self.diff_program:
1771 diff_program = self.diff_program
1772 else:
1773 ext = os.path.splitext(tf.name)[1]
1774 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001775
1776 ttemp = tf.WriteToTemp()
1777 stemp = sf.WriteToTemp()
1778
1779 ext = os.path.splitext(tf.name)[1]
1780
1781 try:
1782 ptemp = tempfile.NamedTemporaryFile()
1783 if isinstance(diff_program, list):
1784 cmd = copy.copy(diff_program)
1785 else:
1786 cmd = [diff_program]
1787 cmd.append(stemp.name)
1788 cmd.append(ttemp.name)
1789 cmd.append(ptemp.name)
1790 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001791 err = []
1792 def run():
1793 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001794 if e:
1795 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001796 th = threading.Thread(target=run)
1797 th.start()
1798 th.join(timeout=300) # 5 mins
1799 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001800 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001801 p.terminate()
1802 th.join(5)
1803 if th.is_alive():
1804 p.kill()
1805 th.join()
1806
Tianjie Xua2a9f992018-01-05 15:15:54 -08001807 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001808 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001809 self.patch = None
1810 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001811 diff = ptemp.read()
1812 finally:
1813 ptemp.close()
1814 stemp.close()
1815 ttemp.close()
1816
1817 self.patch = diff
1818 return self.tf, self.sf, self.patch
1819
1820
1821 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001822 """Returns a tuple of (target_file, source_file, patch_data).
1823
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001824 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001825 computing the patch failed.
1826 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001827 return self.tf, self.sf, self.patch
1828
1829
1830def ComputeDifferences(diffs):
1831 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001832 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001833
1834 # Do the largest files first, to try and reduce the long-pole effect.
1835 by_size = [(i.tf.size, i) for i in diffs]
1836 by_size.sort(reverse=True)
1837 by_size = [i[1] for i in by_size]
1838
1839 lock = threading.Lock()
1840 diff_iter = iter(by_size) # accessed under lock
1841
1842 def worker():
1843 try:
1844 lock.acquire()
1845 for d in diff_iter:
1846 lock.release()
1847 start = time.time()
1848 d.ComputePatch()
1849 dur = time.time() - start
1850 lock.acquire()
1851
1852 tf, sf, patch = d.GetPatch()
1853 if sf.name == tf.name:
1854 name = tf.name
1855 else:
1856 name = "%s (%s)" % (tf.name, sf.name)
1857 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001858 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001859 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001860 logger.info(
1861 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1862 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001863 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001864 except Exception:
1865 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001866 raise
1867
1868 # start worker threads; wait for them all to finish.
1869 threads = [threading.Thread(target=worker)
1870 for i in range(OPTIONS.worker_threads)]
1871 for th in threads:
1872 th.start()
1873 while threads:
1874 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001875
1876
Dan Albert8b72aef2015-03-23 19:13:21 -07001877class BlockDifference(object):
1878 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001879 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001880 self.tgt = tgt
1881 self.src = src
1882 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001883 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001884 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001885
Tao Baodd2a5892015-03-12 12:32:37 -07001886 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001887 version = max(
1888 int(i) for i in
1889 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001890 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001891 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001892
1893 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001894 version=self.version,
1895 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001896 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001897 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001898 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001899 self.touched_src_ranges = b.touched_src_ranges
1900 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001901
Yifan Hong10c530d2018-12-27 17:34:18 -08001902 # On devices with dynamic partitions, for new partitions,
1903 # src is None but OPTIONS.source_info_dict is not.
1904 if OPTIONS.source_info_dict is None:
1905 is_dynamic_build = OPTIONS.info_dict.get(
1906 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001907 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001908 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001909 is_dynamic_build = OPTIONS.source_info_dict.get(
1910 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001911 is_dynamic_source = partition in shlex.split(
1912 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001913
Yifan Hongbb2658d2019-01-25 12:30:58 -08001914 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001915 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1916
Yifan Hongbb2658d2019-01-25 12:30:58 -08001917 # For dynamic partitions builds, check partition list in both source
1918 # and target build because new partitions may be added, and existing
1919 # partitions may be removed.
1920 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1921
Yifan Hong10c530d2018-12-27 17:34:18 -08001922 if is_dynamic:
1923 self.device = 'map_partition("%s")' % partition
1924 else:
1925 if OPTIONS.source_info_dict is None:
1926 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1927 else:
1928 _, device_path = GetTypeAndDevice("/" + partition,
1929 OPTIONS.source_info_dict)
1930 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001931
Tao Baod8d14be2016-02-04 14:26:02 -08001932 @property
1933 def required_cache(self):
1934 return self._required_cache
1935
Tao Bao76def242017-11-21 09:25:31 -08001936 def WriteScript(self, script, output_zip, progress=None,
1937 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001938 if not self.src:
1939 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001940 script.Print("Patching %s image unconditionally..." % (self.partition,))
1941 else:
1942 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001943
Dan Albert8b72aef2015-03-23 19:13:21 -07001944 if progress:
1945 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001946 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001947
1948 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001949 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001950
Tao Bao9bc6bb22015-11-09 16:58:28 -08001951 def WriteStrictVerifyScript(self, script):
1952 """Verify all the blocks in the care_map, including clobbered blocks.
1953
1954 This differs from the WriteVerifyScript() function: a) it prints different
1955 error messages; b) it doesn't allow half-way updated images to pass the
1956 verification."""
1957
1958 partition = self.partition
1959 script.Print("Verifying %s..." % (partition,))
1960 ranges = self.tgt.care_map
1961 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001962 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001963 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1964 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001965 self.device, ranges_str,
1966 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001967 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001968 script.AppendExtra("")
1969
Tao Baod522bdc2016-04-12 15:53:16 -07001970 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001971 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001972
1973 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001974 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001975 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001976
1977 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001978 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001979 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001980 ranges = self.touched_src_ranges
1981 expected_sha1 = self.touched_src_sha1
1982 else:
1983 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1984 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001985
1986 # No blocks to be checked, skipping.
1987 if not ranges:
1988 return
1989
Tao Bao5ece99d2015-05-12 11:42:31 -07001990 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001991 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001992 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001993 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1994 '"%s.patch.dat")) then' % (
1995 self.device, ranges_str, expected_sha1,
1996 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001997 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001998 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001999
Tianjie Xufc3422a2015-12-15 11:53:59 -08002000 if self.version >= 4:
2001
2002 # Bug: 21124327
2003 # When generating incrementals for the system and vendor partitions in
2004 # version 4 or newer, explicitly check the first block (which contains
2005 # the superblock) of the partition to see if it's what we expect. If
2006 # this check fails, give an explicit log message about the partition
2007 # having been remounted R/W (the most likely explanation).
2008 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08002009 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08002010
2011 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07002012 if partition == "system":
2013 code = ErrorCode.SYSTEM_RECOVER_FAILURE
2014 else:
2015 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08002016 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08002017 'ifelse (block_image_recover({device}, "{ranges}") && '
2018 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08002019 'package_extract_file("{partition}.transfer.list"), '
2020 '"{partition}.new.dat", "{partition}.patch.dat"), '
2021 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07002022 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08002023 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07002024 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07002025
Tao Baodd2a5892015-03-12 12:32:37 -07002026 # Abort the OTA update. Note that the incremental OTA cannot be applied
2027 # even if it may match the checksum of the target partition.
2028 # a) If version < 3, operations like move and erase will make changes
2029 # unconditionally and damage the partition.
2030 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08002031 else:
Tianjie Xu209db462016-05-24 17:34:52 -07002032 if partition == "system":
2033 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
2034 else:
2035 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
2036 script.AppendExtra((
2037 'abort("E%d: %s partition has unexpected contents");\n'
2038 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002039
Yifan Hong10c530d2018-12-27 17:34:18 -08002040 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07002041 partition = self.partition
2042 script.Print('Verifying the updated %s image...' % (partition,))
2043 # Unlike pre-install verification, clobbered_blocks should not be ignored.
2044 ranges = self.tgt.care_map
2045 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002046 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002047 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002048 self.device, ranges_str,
2049 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07002050
2051 # Bug: 20881595
2052 # Verify that extended blocks are really zeroed out.
2053 if self.tgt.extended:
2054 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08002055 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08002056 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08002057 self.device, ranges_str,
2058 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07002059 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07002060 if partition == "system":
2061 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
2062 else:
2063 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07002064 script.AppendExtra(
2065 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002066 ' abort("E%d: %s partition has unexpected non-zero contents after '
2067 'OTA update");\n'
2068 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07002069 else:
2070 script.Print('Verified the updated %s image.' % (partition,))
2071
Tianjie Xu209db462016-05-24 17:34:52 -07002072 if partition == "system":
2073 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
2074 else:
2075 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
2076
Tao Bao5fcaaef2015-06-01 13:40:49 -07002077 script.AppendExtra(
2078 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002079 ' abort("E%d: %s partition has unexpected contents after OTA '
2080 'update");\n'
2081 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07002082
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002083 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08002084 ZipWrite(output_zip,
2085 '{}.transfer.list'.format(self.path),
2086 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002087
Tao Bao76def242017-11-21 09:25:31 -08002088 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2089 # its size. Quailty 9 almost triples the compression time but doesn't
2090 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002091 # zip | brotli(quality 6) | brotli(quality 9)
2092 # compressed_size: 942M | 869M (~8% reduced) | 854M
2093 # compression_time: 75s | 265s | 719s
2094 # decompression_time: 15s | 25s | 25s
2095
2096 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002097 brotli_cmd = ['brotli', '--quality=6',
2098 '--output={}.new.dat.br'.format(self.path),
2099 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002100 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002101 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002102
2103 new_data_name = '{}.new.dat.br'.format(self.partition)
2104 ZipWrite(output_zip,
2105 '{}.new.dat.br'.format(self.path),
2106 new_data_name,
2107 compress_type=zipfile.ZIP_STORED)
2108 else:
2109 new_data_name = '{}.new.dat'.format(self.partition)
2110 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2111
Dan Albert8e0178d2015-01-27 15:53:15 -08002112 ZipWrite(output_zip,
2113 '{}.patch.dat'.format(self.path),
2114 '{}.patch.dat'.format(self.partition),
2115 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002116
Tianjie Xu209db462016-05-24 17:34:52 -07002117 if self.partition == "system":
2118 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2119 else:
2120 code = ErrorCode.VENDOR_UPDATE_FAILURE
2121
Yifan Hong10c530d2018-12-27 17:34:18 -08002122 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002123 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002124 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002125 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002126 device=self.device, partition=self.partition,
2127 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002128 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002129
Dan Albert8b72aef2015-03-23 19:13:21 -07002130 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002131 data = source.ReadRangeSet(ranges)
2132 ctx = sha1()
2133
2134 for p in data:
2135 ctx.update(p)
2136
2137 return ctx.hexdigest()
2138
Tao Baoe9b61912015-07-09 17:37:49 -07002139 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2140 """Return the hash value for all zero blocks."""
2141 zero_block = '\x00' * 4096
2142 ctx = sha1()
2143 for _ in range(num_blocks):
2144 ctx.update(zero_block)
2145
2146 return ctx.hexdigest()
2147
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002148
2149DataImage = blockimgdiff.DataImage
Yifan Hong8a66a712019-04-04 15:37:57 -07002150EmptyImage = blockimgdiff.EmptyImage
Tao Bao76def242017-11-21 09:25:31 -08002151
Doug Zongker96a57e72010-09-26 14:57:41 -07002152# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002153PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002154 "ext4": "EMMC",
2155 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002156 "f2fs": "EMMC",
2157 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002158}
Doug Zongker96a57e72010-09-26 14:57:41 -07002159
Tao Bao76def242017-11-21 09:25:31 -08002160
Doug Zongker96a57e72010-09-26 14:57:41 -07002161def GetTypeAndDevice(mount_point, info):
2162 fstab = info["fstab"]
2163 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002164 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2165 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002166 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002167 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002168
2169
2170def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002171 """Parses and converts a PEM-encoded certificate into DER-encoded.
2172
2173 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2174
2175 Returns:
2176 The decoded certificate string.
2177 """
2178 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002179 save = False
2180 for line in data.split("\n"):
2181 if "--END CERTIFICATE--" in line:
2182 break
2183 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002184 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002185 if "--BEGIN CERTIFICATE--" in line:
2186 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002187 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002188 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002189
Tao Bao04e1f012018-02-04 12:13:35 -08002190
2191def ExtractPublicKey(cert):
2192 """Extracts the public key (PEM-encoded) from the given certificate file.
2193
2194 Args:
2195 cert: The certificate filename.
2196
2197 Returns:
2198 The public key string.
2199
2200 Raises:
2201 AssertionError: On non-zero return from 'openssl'.
2202 """
2203 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2204 # While openssl 1.1 writes the key into the given filename followed by '-out',
2205 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2206 # stdout instead.
2207 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2208 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2209 pubkey, stderrdata = proc.communicate()
2210 assert proc.returncode == 0, \
2211 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2212 return pubkey
2213
2214
Tao Bao2cc0ca12019-03-15 10:44:43 -07002215def ExtractAvbPublicKey(key):
2216 """Extracts the AVB public key from the given public or private key.
2217
2218 Args:
2219 key: The input key file, which should be PEM-encoded public or private key.
2220
2221 Returns:
2222 The path to the extracted AVB public key file.
2223 """
2224 output = MakeTempFile(prefix='avb-', suffix='.avbpubkey')
2225 RunAndCheckOutput(
2226 ['avbtool', 'extract_public_key', "--key", key, "--output", output])
2227 return output
2228
2229
Doug Zongker412c02f2014-02-13 10:58:24 -08002230def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2231 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002232 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002233
Tao Bao6d5d6232018-03-09 17:04:42 -08002234 Most of the space in the boot and recovery images is just the kernel, which is
2235 identical for the two, so the resulting patch should be efficient. Add it to
2236 the output zip, along with a shell script that is run from init.rc on first
2237 boot to actually do the patching and install the new recovery image.
2238
2239 Args:
2240 input_dir: The top-level input directory of the target-files.zip.
2241 output_sink: The callback function that writes the result.
2242 recovery_img: File object for the recovery image.
2243 boot_img: File objects for the boot image.
2244 info_dict: A dict returned by common.LoadInfoDict() on the input
2245 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002246 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002247 if info_dict is None:
2248 info_dict = OPTIONS.info_dict
2249
Tao Bao6d5d6232018-03-09 17:04:42 -08002250 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002251
Tao Baof2cffbd2015-07-22 12:33:18 -07002252 if full_recovery_image:
2253 output_sink("etc/recovery.img", recovery_img.data)
2254
2255 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002256 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002257 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002258 # With system-root-image, boot and recovery images will have mismatching
2259 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2260 # to handle such a case.
2261 if system_root_image:
2262 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002263 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002264 assert not os.path.exists(path)
2265 else:
2266 diff_program = ["imgdiff"]
2267 if os.path.exists(path):
2268 diff_program.append("-b")
2269 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002270 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002271 else:
2272 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002273
2274 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2275 _, _, patch = d.ComputePatch()
2276 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002277
Dan Albertebb19aa2015-03-27 19:11:53 -07002278 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002279 # The following GetTypeAndDevice()s need to use the path in the target
2280 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002281 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2282 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2283 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002284 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002285
Tao Baof2cffbd2015-07-22 12:33:18 -07002286 if full_recovery_image:
2287 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002288if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2289 applypatch \\
2290 --flash /system/etc/recovery.img \\
2291 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2292 log -t recovery "Installing new recovery image: succeeded" || \\
2293 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002294else
2295 log -t recovery "Recovery image already installed"
2296fi
2297""" % {'type': recovery_type,
2298 'device': recovery_device,
2299 'sha1': recovery_img.sha1,
2300 'size': recovery_img.size}
2301 else:
2302 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002303if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2304 applypatch %(bonus_args)s \\
2305 --patch /system/recovery-from-boot.p \\
2306 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2307 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2308 log -t recovery "Installing new recovery image: succeeded" || \\
2309 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002310else
2311 log -t recovery "Recovery image already installed"
2312fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002313""" % {'boot_size': boot_img.size,
2314 'boot_sha1': boot_img.sha1,
2315 'recovery_size': recovery_img.size,
2316 'recovery_sha1': recovery_img.sha1,
2317 'boot_type': boot_type,
2318 'boot_device': boot_device,
2319 'recovery_type': recovery_type,
2320 'recovery_device': recovery_device,
2321 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002322
2323 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002324 # in the L release.
2325 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002326
Tao Bao32fcdab2018-10-12 10:30:39 -07002327 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002328
2329 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002330
2331
2332class DynamicPartitionUpdate(object):
2333 def __init__(self, src_group=None, tgt_group=None, progress=None,
2334 block_difference=None):
2335 self.src_group = src_group
2336 self.tgt_group = tgt_group
2337 self.progress = progress
2338 self.block_difference = block_difference
2339
2340 @property
2341 def src_size(self):
2342 if not self.block_difference:
2343 return 0
2344 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2345
2346 @property
2347 def tgt_size(self):
2348 if not self.block_difference:
2349 return 0
2350 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2351
2352 @staticmethod
2353 def _GetSparseImageSize(img):
2354 if not img:
2355 return 0
2356 return img.blocksize * img.total_blocks
2357
2358
2359class DynamicGroupUpdate(object):
2360 def __init__(self, src_size=None, tgt_size=None):
2361 # None: group does not exist. 0: no size limits.
2362 self.src_size = src_size
2363 self.tgt_size = tgt_size
2364
2365
2366class DynamicPartitionsDifference(object):
2367 def __init__(self, info_dict, block_diffs, progress_dict=None,
2368 source_info_dict=None):
2369 if progress_dict is None:
2370 progress_dict = dict()
2371
2372 self._remove_all_before_apply = False
2373 if source_info_dict is None:
2374 self._remove_all_before_apply = True
2375 source_info_dict = dict()
2376
2377 block_diff_dict = {e.partition:e for e in block_diffs}
2378 assert len(block_diff_dict) == len(block_diffs), \
2379 "Duplicated BlockDifference object for {}".format(
2380 [partition for partition, count in
2381 collections.Counter(e.partition for e in block_diffs).items()
2382 if count > 1])
2383
Yifan Hong79997e52019-01-23 16:56:19 -08002384 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002385
2386 for p, block_diff in block_diff_dict.items():
2387 self._partition_updates[p] = DynamicPartitionUpdate()
2388 self._partition_updates[p].block_difference = block_diff
2389
2390 for p, progress in progress_dict.items():
2391 if p in self._partition_updates:
2392 self._partition_updates[p].progress = progress
2393
2394 tgt_groups = shlex.split(info_dict.get(
2395 "super_partition_groups", "").strip())
2396 src_groups = shlex.split(source_info_dict.get(
2397 "super_partition_groups", "").strip())
2398
2399 for g in tgt_groups:
2400 for p in shlex.split(info_dict.get(
2401 "super_%s_partition_list" % g, "").strip()):
2402 assert p in self._partition_updates, \
2403 "{} is in target super_{}_partition_list but no BlockDifference " \
2404 "object is provided.".format(p, g)
2405 self._partition_updates[p].tgt_group = g
2406
2407 for g in src_groups:
2408 for p in shlex.split(source_info_dict.get(
2409 "super_%s_partition_list" % g, "").strip()):
2410 assert p in self._partition_updates, \
2411 "{} is in source super_{}_partition_list but no BlockDifference " \
2412 "object is provided.".format(p, g)
2413 self._partition_updates[p].src_group = g
2414
Yifan Hong45433e42019-01-18 13:55:25 -08002415 target_dynamic_partitions = set(shlex.split(info_dict.get(
2416 "dynamic_partition_list", "").strip()))
2417 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2418 if u.tgt_size)
2419 assert block_diffs_with_target == target_dynamic_partitions, \
2420 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2421 list(target_dynamic_partitions), list(block_diffs_with_target))
2422
2423 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2424 "dynamic_partition_list", "").strip()))
2425 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2426 if u.src_size)
2427 assert block_diffs_with_source == source_dynamic_partitions, \
2428 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2429 list(source_dynamic_partitions), list(block_diffs_with_source))
2430
Yifan Hong10c530d2018-12-27 17:34:18 -08002431 if self._partition_updates:
2432 logger.info("Updating dynamic partitions %s",
2433 self._partition_updates.keys())
2434
Yifan Hong79997e52019-01-23 16:56:19 -08002435 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002436
2437 for g in tgt_groups:
2438 self._group_updates[g] = DynamicGroupUpdate()
2439 self._group_updates[g].tgt_size = int(info_dict.get(
2440 "super_%s_group_size" % g, "0").strip())
2441
2442 for g in src_groups:
2443 if g not in self._group_updates:
2444 self._group_updates[g] = DynamicGroupUpdate()
2445 self._group_updates[g].src_size = int(source_info_dict.get(
2446 "super_%s_group_size" % g, "0").strip())
2447
2448 self._Compute()
2449
2450 def WriteScript(self, script, output_zip, write_verify_script=False):
2451 script.Comment('--- Start patching dynamic partitions ---')
2452 for p, u in self._partition_updates.items():
2453 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2454 script.Comment('Patch partition %s' % p)
2455 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2456 write_verify_script=False)
2457
2458 op_list_path = MakeTempFile()
2459 with open(op_list_path, 'w') as f:
2460 for line in self._op_list:
2461 f.write('{}\n'.format(line))
2462
2463 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2464
2465 script.Comment('Update dynamic partition metadata')
2466 script.AppendExtra('assert(update_dynamic_partitions('
2467 'package_extract_file("dynamic_partitions_op_list")));')
2468
2469 if write_verify_script:
2470 for p, u in self._partition_updates.items():
2471 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2472 u.block_difference.WritePostInstallVerifyScript(script)
2473 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2474
2475 for p, u in self._partition_updates.items():
2476 if u.tgt_size and u.src_size <= u.tgt_size:
2477 script.Comment('Patch partition %s' % p)
2478 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2479 write_verify_script=write_verify_script)
2480 if write_verify_script:
2481 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2482
2483 script.Comment('--- End patching dynamic partitions ---')
2484
2485 def _Compute(self):
2486 self._op_list = list()
2487
2488 def append(line):
2489 self._op_list.append(line)
2490
2491 def comment(line):
2492 self._op_list.append("# %s" % line)
2493
2494 if self._remove_all_before_apply:
2495 comment('Remove all existing dynamic partitions and groups before '
2496 'applying full OTA')
2497 append('remove_all_groups')
2498
2499 for p, u in self._partition_updates.items():
2500 if u.src_group and not u.tgt_group:
2501 append('remove %s' % p)
2502
2503 for p, u in self._partition_updates.items():
2504 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2505 comment('Move partition %s from %s to default' % (p, u.src_group))
2506 append('move %s default' % p)
2507
2508 for p, u in self._partition_updates.items():
2509 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2510 comment('Shrink partition %s from %d to %d' %
2511 (p, u.src_size, u.tgt_size))
2512 append('resize %s %s' % (p, u.tgt_size))
2513
2514 for g, u in self._group_updates.items():
2515 if u.src_size is not None and u.tgt_size is None:
2516 append('remove_group %s' % g)
2517 if (u.src_size is not None and u.tgt_size is not None and
2518 u.src_size > u.tgt_size):
2519 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2520 append('resize_group %s %d' % (g, u.tgt_size))
2521
2522 for g, u in self._group_updates.items():
2523 if u.src_size is None and u.tgt_size is not None:
2524 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2525 append('add_group %s %d' % (g, u.tgt_size))
2526 if (u.src_size is not None and u.tgt_size is not None and
2527 u.src_size < u.tgt_size):
2528 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2529 append('resize_group %s %d' % (g, u.tgt_size))
2530
2531 for p, u in self._partition_updates.items():
2532 if u.tgt_group and not u.src_group:
2533 comment('Add partition %s to group %s' % (p, u.tgt_group))
2534 append('add %s %s' % (p, u.tgt_group))
2535
2536 for p, u in self._partition_updates.items():
2537 if u.tgt_size and u.src_size < u.tgt_size:
2538 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2539 append('resize %s %d' % (p, u.tgt_size))
2540
2541 for p, u in self._partition_updates.items():
2542 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2543 comment('Move partition %s from default to %s' %
2544 (p, u.tgt_group))
2545 append('move %s %s' % (p, u.tgt_group))