blob: 9cda0bd2ba2ddf60ac9cb69f9b6f87cd2dbadbfe [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Yifan Hong10c530d2018-12-27 17:34:18 -080017import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070018import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070019import errno
Doug Zongkereef39442009-04-02 12:14:19 -070020import getopt
21import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010022import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070023import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070024import json
25import logging
26import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070027import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080028import platform
Doug Zongkereef39442009-04-02 12:14:19 -070029import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070030import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070031import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080032import string
Doug Zongkereef39442009-04-02 12:14:19 -070033import subprocess
34import sys
35import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070036import threading
37import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070038import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080039from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070040
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070041import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080042import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070043
Tao Bao32fcdab2018-10-12 10:30:39 -070044logger = logging.getLogger(__name__)
45
Tao Bao986ee862018-10-04 15:46:16 -070046
Dan Albert8b72aef2015-03-23 19:13:21 -070047class Options(object):
48 def __init__(self):
Pavel Salomatov32676552019-03-06 20:00:45 +030049 base_out_path = os.getenv('OUT_DIR_COMMON_BASE')
50 if base_out_path is None:
51 base_search_path = "out"
52 else:
53 base_search_path = os.path.join(base_out_path, os.path.basename(os.getcwd()))
54
Dan Albert8b72aef2015-03-23 19:13:21 -070055 platform_search_path = {
Pavel Salomatov32676552019-03-06 20:00:45 +030056 "linux2": os.path.join(base_search_path, "host/linux-x86"),
57 "darwin": os.path.join(base_search_path, "host/darwin-x86"),
Doug Zongker85448772014-09-09 14:59:20 -070058 }
Doug Zongker85448772014-09-09 14:59:20 -070059
Tao Bao76def242017-11-21 09:25:31 -080060 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070061 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080062 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070063 self.extra_signapk_args = []
64 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080065 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070066 self.public_key_suffix = ".x509.pem"
67 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070068 # use otatools built boot_signer by default
69 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070070 self.boot_signer_args = []
71 self.verity_signer_path = None
72 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070073 self.verbose = False
74 self.tempfiles = []
75 self.device_specific = None
76 self.extras = {}
77 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070078 self.source_info_dict = None
79 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070080 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070081 # Stash size cannot exceed cache_size * threshold.
82 self.cache_size = None
83 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070084
85
86OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070087
Tao Bao71197512018-10-11 14:08:45 -070088# The block size that's used across the releasetools scripts.
89BLOCK_SIZE = 4096
90
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080091# Values for "certificate" in apkcerts that mean special things.
92SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
93
Tao Bao9dd909e2017-11-14 11:27:32 -080094# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010095AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010096 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080097
Tianjie Xu861f4132018-09-12 11:49:33 -070098# Partitions that should have their care_map added to META/care_map.pb
99PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
100 'odm')
101
102
Tianjie Xu209db462016-05-24 17:34:52 -0700103class ErrorCode(object):
104 """Define error_codes for failures that happen during the actual
105 update package installation.
106
107 Error codes 0-999 are reserved for failures before the package
108 installation (i.e. low battery, package verification failure).
109 Detailed code in 'bootable/recovery/error_code.h' """
110
111 SYSTEM_VERIFICATION_FAILURE = 1000
112 SYSTEM_UPDATE_FAILURE = 1001
113 SYSTEM_UNEXPECTED_CONTENTS = 1002
114 SYSTEM_NONZERO_CONTENTS = 1003
115 SYSTEM_RECOVER_FAILURE = 1004
116 VENDOR_VERIFICATION_FAILURE = 2000
117 VENDOR_UPDATE_FAILURE = 2001
118 VENDOR_UNEXPECTED_CONTENTS = 2002
119 VENDOR_NONZERO_CONTENTS = 2003
120 VENDOR_RECOVER_FAILURE = 2004
121 OEM_PROP_MISMATCH = 3000
122 FINGERPRINT_MISMATCH = 3001
123 THUMBPRINT_MISMATCH = 3002
124 OLDER_BUILD = 3003
125 DEVICE_MISMATCH = 3004
126 BAD_PATCH_FILE = 3005
127 INSUFFICIENT_CACHE_SPACE = 3006
128 TUNE_PARTITION_FAILURE = 3007
129 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800130
Tao Bao80921982018-03-21 21:02:19 -0700131
Dan Albert8b72aef2015-03-23 19:13:21 -0700132class ExternalError(RuntimeError):
133 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700134
135
Tao Bao32fcdab2018-10-12 10:30:39 -0700136def InitLogging():
137 DEFAULT_LOGGING_CONFIG = {
138 'version': 1,
139 'disable_existing_loggers': False,
140 'formatters': {
141 'standard': {
142 'format':
143 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
144 'datefmt': '%Y-%m-%d %H:%M:%S',
145 },
146 },
147 'handlers': {
148 'default': {
149 'class': 'logging.StreamHandler',
150 'formatter': 'standard',
151 },
152 },
153 'loggers': {
154 '': {
155 'handlers': ['default'],
156 'level': 'WARNING',
157 'propagate': True,
158 }
159 }
160 }
161 env_config = os.getenv('LOGGING_CONFIG')
162 if env_config:
163 with open(env_config) as f:
164 config = json.load(f)
165 else:
166 config = DEFAULT_LOGGING_CONFIG
167
168 # Increase the logging level for verbose mode.
169 if OPTIONS.verbose:
170 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
171 config['loggers']['']['level'] = 'INFO'
172
173 logging.config.dictConfig(config)
174
175
Tao Bao39451582017-05-04 11:10:47 -0700176def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700177 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700178
Tao Bao73dd4f42018-10-04 16:25:33 -0700179 Args:
180 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700181 verbose: Whether the commands should be shown. Default to the global
182 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700183 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
184 stdin, etc. stdout and stderr will default to subprocess.PIPE and
185 subprocess.STDOUT respectively unless caller specifies any of them.
186
187 Returns:
188 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700189 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700190 if 'stdout' not in kwargs and 'stderr' not in kwargs:
191 kwargs['stdout'] = subprocess.PIPE
192 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700193 # Don't log any if caller explicitly says so.
194 if verbose != False:
195 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700196 return subprocess.Popen(args, **kwargs)
197
198
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800199def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800200 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800201
202 Args:
203 args: The command represented as a list of strings.
204 verbose: Whether the commands should be shown. Default to the global
205 verbosity if unspecified.
206 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
207 stdin, etc. stdout and stderr will default to subprocess.PIPE and
208 subprocess.STDOUT respectively unless caller specifies any of them.
209
Bill Peckham889b0c62019-02-21 18:53:37 -0800210 Raises:
211 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800212 """
213 proc = Run(args, verbose=verbose, **kwargs)
214 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800215
216 if proc.returncode != 0:
217 raise ExternalError(
218 "Failed to run command '{}' (exit code {})".format(
219 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800220
221
Tao Bao986ee862018-10-04 15:46:16 -0700222def RunAndCheckOutput(args, verbose=None, **kwargs):
223 """Runs the given command and returns the output.
224
225 Args:
226 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700227 verbose: Whether the commands should be shown. Default to the global
228 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700229 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
230 stdin, etc. stdout and stderr will default to subprocess.PIPE and
231 subprocess.STDOUT respectively unless caller specifies any of them.
232
233 Returns:
234 The output string.
235
236 Raises:
237 ExternalError: On non-zero exit from the command.
238 """
Tao Bao986ee862018-10-04 15:46:16 -0700239 proc = Run(args, verbose=verbose, **kwargs)
240 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700241 # Don't log any if caller explicitly says so.
242 if verbose != False:
243 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700244 if proc.returncode != 0:
245 raise ExternalError(
246 "Failed to run command '{}' (exit code {}):\n{}".format(
247 args, proc.returncode, output))
248 return output
249
250
Tao Baoc765cca2018-01-31 17:32:40 -0800251def RoundUpTo4K(value):
252 rounded_up = value + 4095
253 return rounded_up - (rounded_up % 4096)
254
255
Ying Wang7e6d4e42010-12-13 16:25:36 -0800256def CloseInheritedPipes():
257 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
258 before doing other work."""
259 if platform.system() != "Darwin":
260 return
261 for d in range(3, 1025):
262 try:
263 stat = os.fstat(d)
264 if stat is not None:
265 pipebit = stat[0] & 0x1000
266 if pipebit != 0:
267 os.close(d)
268 except OSError:
269 pass
270
271
Tao Bao410ad8b2018-08-24 12:08:38 -0700272def LoadInfoDict(input_file, repacking=False):
273 """Loads the key/value pairs from the given input target_files.
274
275 It reads `META/misc_info.txt` file in the target_files input, does sanity
276 checks and returns the parsed key/value pairs for to the given build. It's
277 usually called early when working on input target_files files, e.g. when
278 generating OTAs, or signing builds. Note that the function may be called
279 against an old target_files file (i.e. from past dessert releases). So the
280 property parsing needs to be backward compatible.
281
282 In a `META/misc_info.txt`, a few properties are stored as links to the files
283 in the PRODUCT_OUT directory. It works fine with the build system. However,
284 they are no longer available when (re)generating images from target_files zip.
285 When `repacking` is True, redirect these properties to the actual files in the
286 unzipped directory.
287
288 Args:
289 input_file: The input target_files file, which could be an open
290 zipfile.ZipFile instance, or a str for the dir that contains the files
291 unzipped from a target_files file.
292 repacking: Whether it's trying repack an target_files file after loading the
293 info dict (default: False). If so, it will rewrite a few loaded
294 properties (e.g. selinux_fc, root_dir) to point to the actual files in
295 target_files file. When doing repacking, `input_file` must be a dir.
296
297 Returns:
298 A dict that contains the parsed key/value pairs.
299
300 Raises:
301 AssertionError: On invalid input arguments.
302 ValueError: On malformed input values.
303 """
304 if repacking:
305 assert isinstance(input_file, str), \
306 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700307
Doug Zongkerc9253822014-02-04 12:17:58 -0800308 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700309 if isinstance(input_file, zipfile.ZipFile):
310 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800311 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700312 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800313 try:
314 with open(path) as f:
315 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700316 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800317 if e.errno == errno.ENOENT:
318 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800319
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700320 try:
Michael Runge6e836112014-04-15 17:40:21 -0700321 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700322 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700323 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700324
Tao Bao410ad8b2018-08-24 12:08:38 -0700325 if "recovery_api_version" not in d:
326 raise ValueError("Failed to find 'recovery_api_version'")
327 if "fstab_version" not in d:
328 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800329
Tao Bao410ad8b2018-08-24 12:08:38 -0700330 if repacking:
331 # We carry a copy of file_contexts.bin under META/. If not available, search
332 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
333 # images than the one running on device, in that case, we must have the one
334 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700335 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700336 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700337 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700338
Tom Cherryd14b8952018-08-09 14:26:00 -0700339 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700340
Tom Cherryd14b8952018-08-09 14:26:00 -0700341 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700342 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700343 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700344 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700345
Tao Baof54216f2016-03-29 15:12:37 -0700346 # Redirect {system,vendor}_base_fs_file.
347 if "system_base_fs_file" in d:
348 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700349 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700350 if os.path.exists(system_base_fs_file):
351 d["system_base_fs_file"] = system_base_fs_file
352 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700353 logger.warning(
354 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700355 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700356
357 if "vendor_base_fs_file" in d:
358 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700359 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700360 if os.path.exists(vendor_base_fs_file):
361 d["vendor_base_fs_file"] = vendor_base_fs_file
362 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700363 logger.warning(
364 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700365 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700366
Doug Zongker37974732010-09-16 17:44:38 -0700367 def makeint(key):
368 if key in d:
369 d[key] = int(d[key], 0)
370
371 makeint("recovery_api_version")
372 makeint("blocksize")
373 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700374 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700375 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700376 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700377 makeint("recovery_size")
378 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800379 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700380
Tao Baoa57ab9f2018-08-24 12:08:38 -0700381 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
382 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
383 # cases, since it may load the info_dict from an old build (e.g. when
384 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800385 system_root_image = d.get("system_root_image") == "true"
386 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700387 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700388 if isinstance(input_file, zipfile.ZipFile):
389 if recovery_fstab_path not in input_file.namelist():
390 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
391 else:
392 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
393 if not os.path.exists(path):
394 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800395 d["fstab"] = LoadRecoveryFSTab(
396 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700397
Tao Bao76def242017-11-21 09:25:31 -0800398 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700399 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700400 if isinstance(input_file, zipfile.ZipFile):
401 if recovery_fstab_path not in input_file.namelist():
402 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
403 else:
404 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
405 if not os.path.exists(path):
406 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800407 d["fstab"] = LoadRecoveryFSTab(
408 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700409
Tianjie Xucfa86222016-03-07 16:31:19 -0800410 else:
411 d["fstab"] = None
412
Tianjie Xu861f4132018-09-12 11:49:33 -0700413 # Tries to load the build props for all partitions with care_map, including
414 # system and vendor.
415 for partition in PARTITIONS_WITH_CARE_MAP:
416 d["{}.build.prop".format(partition)] = LoadBuildProp(
417 read_helper, "{}/build.prop".format(partition.upper()))
418 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800419
420 # Set up the salt (based on fingerprint or thumbprint) that will be used when
421 # adding AVB footer.
422 if d.get("avb_enable") == "true":
423 fp = None
424 if "build.prop" in d:
425 build_prop = d["build.prop"]
426 if "ro.build.fingerprint" in build_prop:
427 fp = build_prop["ro.build.fingerprint"]
428 elif "ro.build.thumbprint" in build_prop:
429 fp = build_prop["ro.build.thumbprint"]
430 if fp:
431 d["avb_salt"] = sha256(fp).hexdigest()
432
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700433 return d
434
Tao Baod1de6f32017-03-01 16:38:48 -0800435
Tao Baobcd1d162017-08-26 13:10:26 -0700436def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700437 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700438 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700439 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700440 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700441 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700442 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700443
Tao Baod1de6f32017-03-01 16:38:48 -0800444
Michael Runge6e836112014-04-15 17:40:21 -0700445def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700446 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700447 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700448 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700449 if not line or line.startswith("#"):
450 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700451 if "=" in line:
452 name, value = line.split("=", 1)
453 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700454 return d
455
Tao Baod1de6f32017-03-01 16:38:48 -0800456
Tianjie Xucfa86222016-03-07 16:31:19 -0800457def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
458 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700459 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800460 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700461 self.mount_point = mount_point
462 self.fs_type = fs_type
463 self.device = device
464 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700465 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700466
467 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800468 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700469 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700470 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700471 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700472
Tao Baod1de6f32017-03-01 16:38:48 -0800473 assert fstab_version == 2
474
475 d = {}
476 for line in data.split("\n"):
477 line = line.strip()
478 if not line or line.startswith("#"):
479 continue
480
481 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
482 pieces = line.split()
483 if len(pieces) != 5:
484 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
485
486 # Ignore entries that are managed by vold.
487 options = pieces[4]
488 if "voldmanaged=" in options:
489 continue
490
491 # It's a good line, parse it.
492 length = 0
493 options = options.split(",")
494 for i in options:
495 if i.startswith("length="):
496 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800497 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800498 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700499 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800500
Tao Baod1de6f32017-03-01 16:38:48 -0800501 mount_flags = pieces[3]
502 # Honor the SELinux context if present.
503 context = None
504 for i in mount_flags.split(","):
505 if i.startswith("context="):
506 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800507
Tao Baod1de6f32017-03-01 16:38:48 -0800508 mount_point = pieces[1]
509 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
510 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800511
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700512 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700513 # system. Other areas assume system is always at "/system" so point /system
514 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700515 if system_root_image:
516 assert not d.has_key("/system") and d.has_key("/")
517 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700518 return d
519
520
Doug Zongker37974732010-09-16 17:44:38 -0700521def DumpInfoDict(d):
522 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700523 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700524
Dan Albert8b72aef2015-03-23 19:13:21 -0700525
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800526def AppendAVBSigningArgs(cmd, partition):
527 """Append signing arguments for avbtool."""
528 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
529 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
530 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
531 if key_path and algorithm:
532 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700533 avb_salt = OPTIONS.info_dict.get("avb_salt")
534 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700535 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700536 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800537
538
Tao Bao02a08592018-07-22 12:40:45 -0700539def GetAvbChainedPartitionArg(partition, info_dict, key=None):
540 """Constructs and returns the arg to build or verify a chained partition.
541
542 Args:
543 partition: The partition name.
544 info_dict: The info dict to look up the key info and rollback index
545 location.
546 key: The key to be used for building or verifying the partition. Defaults to
547 the key listed in info_dict.
548
549 Returns:
550 A string of form "partition:rollback_index_location:key" that can be used to
551 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700552 """
553 if key is None:
554 key = info_dict["avb_" + partition + "_key_path"]
555 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
556 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
Tao Bao986ee862018-10-04 15:46:16 -0700557 RunAndCheckOutput(
Tao Bao73dd4f42018-10-04 16:25:33 -0700558 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path])
Tao Bao02a08592018-07-22 12:40:45 -0700559
560 rollback_index_location = info_dict[
561 "avb_" + partition + "_rollback_index_location"]
562 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
563
564
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700565def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800566 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700567 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700568
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700569 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800570 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
571 we are building a two-step special image (i.e. building a recovery image to
572 be loaded into /boot in two-step OTAs).
573
574 Return the image data, or None if sourcedir does not appear to contains files
575 for building the requested image.
576 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700577
578 def make_ramdisk():
579 ramdisk_img = tempfile.NamedTemporaryFile()
580
581 if os.access(fs_config_file, os.F_OK):
582 cmd = ["mkbootfs", "-f", fs_config_file,
583 os.path.join(sourcedir, "RAMDISK")]
584 else:
585 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
586 p1 = Run(cmd, stdout=subprocess.PIPE)
587 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
588
589 p2.wait()
590 p1.wait()
591 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
592 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
593
594 return ramdisk_img
595
596 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
597 return None
598
599 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700600 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700601
Doug Zongkerd5131602012-08-02 14:46:42 -0700602 if info_dict is None:
603 info_dict = OPTIONS.info_dict
604
Doug Zongkereef39442009-04-02 12:14:19 -0700605 img = tempfile.NamedTemporaryFile()
606
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700607 if has_ramdisk:
608 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700609
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800610 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
611 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
612
613 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700614
Benoit Fradina45a8682014-07-14 21:00:43 +0200615 fn = os.path.join(sourcedir, "second")
616 if os.access(fn, os.F_OK):
617 cmd.append("--second")
618 cmd.append(fn)
619
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800620 fn = os.path.join(sourcedir, "dtb")
621 if os.access(fn, os.F_OK):
622 cmd.append("--dtb")
623 cmd.append(fn)
624
Doug Zongker171f1cd2009-06-15 22:36:37 -0700625 fn = os.path.join(sourcedir, "cmdline")
626 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700627 cmd.append("--cmdline")
628 cmd.append(open(fn).read().rstrip("\n"))
629
630 fn = os.path.join(sourcedir, "base")
631 if os.access(fn, os.F_OK):
632 cmd.append("--base")
633 cmd.append(open(fn).read().rstrip("\n"))
634
Ying Wang4de6b5b2010-08-25 14:29:34 -0700635 fn = os.path.join(sourcedir, "pagesize")
636 if os.access(fn, os.F_OK):
637 cmd.append("--pagesize")
638 cmd.append(open(fn).read().rstrip("\n"))
639
Tao Bao76def242017-11-21 09:25:31 -0800640 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700641 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700642 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700643
Tao Bao76def242017-11-21 09:25:31 -0800644 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000645 if args and args.strip():
646 cmd.extend(shlex.split(args))
647
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700648 if has_ramdisk:
649 cmd.extend(["--ramdisk", ramdisk_img.name])
650
Tao Baod95e9fd2015-03-29 23:07:41 -0700651 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800652 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700653 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700654 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700655 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700656 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700657
Tao Baobf70c3182017-07-11 17:27:55 -0700658 # "boot" or "recovery", without extension.
659 partition_name = os.path.basename(sourcedir).lower()
660
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800661 if partition_name == "recovery":
662 if info_dict.get("include_recovery_dtbo") == "true":
663 fn = os.path.join(sourcedir, "recovery_dtbo")
664 cmd.extend(["--recovery_dtbo", fn])
665 if info_dict.get("include_recovery_acpio") == "true":
666 fn = os.path.join(sourcedir, "recovery_acpio")
667 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700668
Tao Bao986ee862018-10-04 15:46:16 -0700669 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700670
Tao Bao76def242017-11-21 09:25:31 -0800671 if (info_dict.get("boot_signer") == "true" and
672 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800673 # Hard-code the path as "/boot" for two-step special recovery image (which
674 # will be loaded into /boot during the two-step OTA).
675 if two_step_image:
676 path = "/boot"
677 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700678 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700679 cmd = [OPTIONS.boot_signer_path]
680 cmd.extend(OPTIONS.boot_signer_args)
681 cmd.extend([path, img.name,
682 info_dict["verity_key"] + ".pk8",
683 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700684 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700685
Tao Baod95e9fd2015-03-29 23:07:41 -0700686 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800687 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700688 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700689 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800690 # We have switched from the prebuilt futility binary to using the tool
691 # (futility-host) built from the source. Override the setting in the old
692 # TF.zip.
693 futility = info_dict["futility"]
694 if futility.startswith("prebuilts/"):
695 futility = "futility-host"
696 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700697 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700698 info_dict["vboot_key"] + ".vbprivk",
699 info_dict["vboot_subkey"] + ".vbprivk",
700 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700701 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700702 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700703
Tao Baof3282b42015-04-01 11:21:55 -0700704 # Clean up the temp files.
705 img_unsigned.close()
706 img_keyblock.close()
707
David Zeuthen8fecb282017-12-01 16:24:01 -0500708 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800709 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700710 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500711 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400712 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700713 "--partition_size", str(part_size), "--partition_name",
714 partition_name]
715 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500716 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400717 if args and args.strip():
718 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700719 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500720
721 img.seek(os.SEEK_SET, 0)
722 data = img.read()
723
724 if has_ramdisk:
725 ramdisk_img.close()
726 img.close()
727
728 return data
729
730
Doug Zongkerd5131602012-08-02 14:46:42 -0700731def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800732 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700733 """Return a File object with the desired bootable image.
734
735 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
736 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
737 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700738
Doug Zongker55d93282011-01-25 17:03:34 -0800739 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
740 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700741 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800742 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700743
744 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
745 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700746 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700747 return File.FromLocalFile(name, prebuilt_path)
748
Tao Bao32fcdab2018-10-12 10:30:39 -0700749 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700750
751 if info_dict is None:
752 info_dict = OPTIONS.info_dict
753
754 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800755 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
756 # for recovery.
757 has_ramdisk = (info_dict.get("system_root_image") != "true" or
758 prebuilt_name != "boot.img" or
759 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700760
Doug Zongker6f1d0312014-08-22 08:07:12 -0700761 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400762 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
763 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800764 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700765 if data:
766 return File(name, data)
767 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800768
Doug Zongkereef39442009-04-02 12:14:19 -0700769
Narayan Kamatha07bf042017-08-14 14:49:21 +0100770def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800771 """Gunzips the given gzip compressed file to a given output file."""
772 with gzip.open(in_filename, "rb") as in_file, \
773 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100774 shutil.copyfileobj(in_file, out_file)
775
776
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800777def UnzipToDir(filename, dirname, pattern=None):
778 """Unzips the archive to the given directory.
779
780 Args:
781 filename: The name of the zip file to unzip.
782
783 dirname: Where the unziped files will land.
784
785 pattern: Files to unzip from the archive. If omitted, will unzip the entire
786 archvie.
787 """
788
789 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
790 if pattern is not None:
791 cmd.extend(pattern)
792 RunAndCheckOutput(cmd)
793
794
Doug Zongker75f17362009-12-08 13:46:44 -0800795def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800796 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800797
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800798 Args:
799 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
800 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
801
802 pattern: Files to unzip from the archive. If omitted, will unzip the entire
803 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800804
Tao Bao1c830bf2017-12-25 10:43:47 -0800805 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800806 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800807 """
Doug Zongkereef39442009-04-02 12:14:19 -0700808
Tao Bao1c830bf2017-12-25 10:43:47 -0800809 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800810 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
811 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800812 UnzipToDir(m.group(1), tmp, pattern)
813 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800814 filename = m.group(1)
815 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800816 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800817
Tao Baodba59ee2018-01-09 13:21:02 -0800818 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700819
820
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700821def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
822 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800823 """Returns a SparseImage object suitable for passing to BlockImageDiff.
824
825 This function loads the specified sparse image from the given path, and
826 performs additional processing for OTA purpose. For example, it always adds
827 block 0 to clobbered blocks list. It also detects files that cannot be
828 reconstructed from the block list, for whom we should avoid applying imgdiff.
829
830 Args:
831 which: The partition name, which must be "system" or "vendor".
832 tmpdir: The directory that contains the prebuilt image and block map file.
833 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800834 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700835 hashtree_info_generator: If present, generates the hashtree_info for this
836 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800837 Returns:
838 A SparseImage object, with file_map info loaded.
839 """
840 assert which in ("system", "vendor")
841
842 path = os.path.join(tmpdir, "IMAGES", which + ".img")
843 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
844
845 # The image and map files must have been created prior to calling
846 # ota_from_target_files.py (since LMP).
847 assert os.path.exists(path) and os.path.exists(mappath)
848
849 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
850 # it to clobbered_blocks so that it will be written to the target
851 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
852 clobbered_blocks = "0"
853
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700854 image = sparse_img.SparseImage(
855 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
856 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800857
858 # block.map may contain less blocks, because mke2fs may skip allocating blocks
859 # if they contain all zeros. We can't reconstruct such a file from its block
860 # list. Tag such entries accordingly. (Bug: 65213616)
861 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800862 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700863 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800864 continue
865
Tom Cherryd14b8952018-08-09 14:26:00 -0700866 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
867 # filename listed in system.map may contain an additional leading slash
868 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
869 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700870 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
871
Tom Cherryd14b8952018-08-09 14:26:00 -0700872 # Special handling another case, where files not under /system
873 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700874 if which == 'system' and not arcname.startswith('SYSTEM'):
875 arcname = 'ROOT/' + arcname
876
877 assert arcname in input_zip.namelist(), \
878 "Failed to find the ZIP entry for {}".format(entry)
879
Tao Baoc765cca2018-01-31 17:32:40 -0800880 info = input_zip.getinfo(arcname)
881 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800882
883 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800884 # image, check the original block list to determine its completeness. Note
885 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800886 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800887 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800888
Tao Baoc765cca2018-01-31 17:32:40 -0800889 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
890 ranges.extra['incomplete'] = True
891
892 return image
893
894
Doug Zongkereef39442009-04-02 12:14:19 -0700895def GetKeyPasswords(keylist):
896 """Given a list of keys, prompt the user to enter passwords for
897 those which require them. Return a {key: password} dict. password
898 will be None if the key has no password."""
899
Doug Zongker8ce7c252009-05-22 13:34:54 -0700900 no_passwords = []
901 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700902 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700903 devnull = open("/dev/null", "w+b")
904 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800905 # We don't need a password for things that aren't really keys.
906 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700907 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700908 continue
909
T.R. Fullhart37e10522013-03-18 10:31:26 -0700910 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700911 "-inform", "DER", "-nocrypt"],
912 stdin=devnull.fileno(),
913 stdout=devnull.fileno(),
914 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700915 p.communicate()
916 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700917 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700918 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700919 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700920 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
921 "-inform", "DER", "-passin", "pass:"],
922 stdin=devnull.fileno(),
923 stdout=devnull.fileno(),
924 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700925 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700926 if p.returncode == 0:
927 # Encrypted key with empty string as password.
928 key_passwords[k] = ''
929 elif stderr.startswith('Error decrypting key'):
930 # Definitely encrypted key.
931 # It would have said "Error reading key" if it didn't parse correctly.
932 need_passwords.append(k)
933 else:
934 # Potentially, a type of key that openssl doesn't understand.
935 # We'll let the routines in signapk.jar handle it.
936 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700937 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700938
T.R. Fullhart37e10522013-03-18 10:31:26 -0700939 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800940 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700941 return key_passwords
942
943
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800944def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700945 """Gets the minSdkVersion declared in the APK.
946
947 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
948 This can be both a decimal number (API Level) or a codename.
949
950 Args:
951 apk_name: The APK filename.
952
953 Returns:
954 The parsed SDK version string.
955
956 Raises:
957 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800958 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700959 proc = Run(
960 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
961 stderr=subprocess.PIPE)
962 stdoutdata, stderrdata = proc.communicate()
963 if proc.returncode != 0:
964 raise ExternalError(
965 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
966 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800967
Tao Baof47bf0f2018-03-21 23:28:51 -0700968 for line in stdoutdata.split("\n"):
969 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800970 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
971 if m:
972 return m.group(1)
973 raise ExternalError("No minSdkVersion returned by aapt")
974
975
976def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700977 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800978
Tao Baof47bf0f2018-03-21 23:28:51 -0700979 If minSdkVersion is set to a codename, it is translated to a number using the
980 provided map.
981
982 Args:
983 apk_name: The APK filename.
984
985 Returns:
986 The parsed SDK version number.
987
988 Raises:
989 ExternalError: On failing to get the min SDK version number.
990 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800991 version = GetMinSdkVersion(apk_name)
992 try:
993 return int(version)
994 except ValueError:
995 # Not a decimal number. Codename?
996 if version in codename_to_api_level_map:
997 return codename_to_api_level_map[version]
998 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700999 raise ExternalError(
1000 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
1001 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001002
1003
1004def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -08001005 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -07001006 """Sign the input_name zip/jar/apk, producing output_name. Use the
1007 given key and password (the latter may be None if the key does not
1008 have a password.
1009
Doug Zongker951495f2009-08-14 12:44:19 -07001010 If whole_file is true, use the "-w" option to SignApk to embed a
1011 signature that covers the whole file in the archive comment of the
1012 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001013
1014 min_api_level is the API Level (int) of the oldest platform this file may end
1015 up on. If not specified for an APK, the API Level is obtained by interpreting
1016 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1017
1018 codename_to_api_level_map is needed to translate the codename which may be
1019 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -07001020 """
Tao Bao76def242017-11-21 09:25:31 -08001021 if codename_to_api_level_map is None:
1022 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -07001023
Alex Klyubin9667b182015-12-10 13:38:50 -08001024 java_library_path = os.path.join(
1025 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1026
Tao Baoe95540e2016-11-08 12:08:53 -08001027 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1028 ["-Djava.library.path=" + java_library_path,
1029 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
1030 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001031 if whole_file:
1032 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001033
1034 min_sdk_version = min_api_level
1035 if min_sdk_version is None:
1036 if not whole_file:
1037 min_sdk_version = GetMinSdkVersionInt(
1038 input_name, codename_to_api_level_map)
1039 if min_sdk_version is not None:
1040 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1041
T.R. Fullhart37e10522013-03-18 10:31:26 -07001042 cmd.extend([key + OPTIONS.public_key_suffix,
1043 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001044 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001045
Tao Bao73dd4f42018-10-04 16:25:33 -07001046 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001047 if password is not None:
1048 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001049 stdoutdata, _ = proc.communicate(password)
1050 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001051 raise ExternalError(
1052 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001053 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001054
Doug Zongkereef39442009-04-02 12:14:19 -07001055
Doug Zongker37974732010-09-16 17:44:38 -07001056def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001057 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001058
Tao Bao9dd909e2017-11-14 11:27:32 -08001059 For non-AVB images, raise exception if the data is too big. Print a warning
1060 if the data is nearing the maximum size.
1061
1062 For AVB images, the actual image size should be identical to the limit.
1063
1064 Args:
1065 data: A string that contains all the data for the partition.
1066 target: The partition name. The ".img" suffix is optional.
1067 info_dict: The dict to be looked up for relevant info.
1068 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001069 if target.endswith(".img"):
1070 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001071 mount_point = "/" + target
1072
Ying Wangf8824af2014-06-03 14:07:27 -07001073 fs_type = None
1074 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001075 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001076 if mount_point == "/userdata":
1077 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001078 p = info_dict["fstab"][mount_point]
1079 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001080 device = p.device
1081 if "/" in device:
1082 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001083 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001084 if not fs_type or not limit:
1085 return
Doug Zongkereef39442009-04-02 12:14:19 -07001086
Andrew Boie0f9aec82012-02-14 09:32:52 -08001087 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001088 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1089 # path.
1090 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1091 if size != limit:
1092 raise ExternalError(
1093 "Mismatching image size for %s: expected %d actual %d" % (
1094 target, limit, size))
1095 else:
1096 pct = float(size) * 100.0 / limit
1097 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1098 if pct >= 99.0:
1099 raise ExternalError(msg)
1100 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001101 logger.warning("\n WARNING: %s\n", msg)
1102 else:
1103 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001104
1105
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001106def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001107 """Parses the APK certs info from a given target-files zip.
1108
1109 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1110 tuple with the following elements: (1) a dictionary that maps packages to
1111 certs (based on the "certificate" and "private_key" attributes in the file;
1112 (2) a string representing the extension of compressed APKs in the target files
1113 (e.g ".gz", ".bro").
1114
1115 Args:
1116 tf_zip: The input target_files ZipFile (already open).
1117
1118 Returns:
1119 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1120 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1121 no compressed APKs.
1122 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001123 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001124 compressed_extension = None
1125
Tao Bao0f990332017-09-08 19:02:54 -07001126 # META/apkcerts.txt contains the info for _all_ the packages known at build
1127 # time. Filter out the ones that are not installed.
1128 installed_files = set()
1129 for name in tf_zip.namelist():
1130 basename = os.path.basename(name)
1131 if basename:
1132 installed_files.add(basename)
1133
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001134 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1135 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001136 if not line:
1137 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001138 m = re.match(
1139 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1140 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1141 line)
1142 if not m:
1143 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001144
Tao Bao818ddf52018-01-05 11:17:34 -08001145 matches = m.groupdict()
1146 cert = matches["CERT"]
1147 privkey = matches["PRIVKEY"]
1148 name = matches["NAME"]
1149 this_compressed_extension = matches["COMPRESSED"]
1150
1151 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1152 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1153 if cert in SPECIAL_CERT_STRINGS and not privkey:
1154 certmap[name] = cert
1155 elif (cert.endswith(OPTIONS.public_key_suffix) and
1156 privkey.endswith(OPTIONS.private_key_suffix) and
1157 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1158 certmap[name] = cert[:-public_key_suffix_len]
1159 else:
1160 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1161
1162 if not this_compressed_extension:
1163 continue
1164
1165 # Only count the installed files.
1166 filename = name + '.' + this_compressed_extension
1167 if filename not in installed_files:
1168 continue
1169
1170 # Make sure that all the values in the compression map have the same
1171 # extension. We don't support multiple compression methods in the same
1172 # system image.
1173 if compressed_extension:
1174 if this_compressed_extension != compressed_extension:
1175 raise ValueError(
1176 "Multiple compressed extensions: {} vs {}".format(
1177 compressed_extension, this_compressed_extension))
1178 else:
1179 compressed_extension = this_compressed_extension
1180
1181 return (certmap,
1182 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001183
1184
Doug Zongkereef39442009-04-02 12:14:19 -07001185COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001186Global options
1187
1188 -p (--path) <dir>
1189 Prepend <dir>/bin to the list of places to search for binaries run by this
1190 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001191
Doug Zongker05d3dea2009-06-22 11:32:31 -07001192 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001193 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001194
Tao Bao30df8b42018-04-23 15:32:53 -07001195 -x (--extra) <key=value>
1196 Add a key/value pair to the 'extras' dict, which device-specific extension
1197 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001198
Doug Zongkereef39442009-04-02 12:14:19 -07001199 -v (--verbose)
1200 Show command lines being executed.
1201
1202 -h (--help)
1203 Display this usage message and exit.
1204"""
1205
1206def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001207 print(docstring.rstrip("\n"))
1208 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001209
1210
1211def ParseOptions(argv,
1212 docstring,
1213 extra_opts="", extra_long_opts=(),
1214 extra_option_handler=None):
1215 """Parse the options in argv and return any arguments that aren't
1216 flags. docstring is the calling module's docstring, to be displayed
1217 for errors and -h. extra_opts and extra_long_opts are for flags
1218 defined by the caller, which are processed by passing them to
1219 extra_option_handler."""
1220
1221 try:
1222 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001223 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001224 ["help", "verbose", "path=", "signapk_path=",
1225 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001226 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001227 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1228 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001229 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001230 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001231 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001232 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001233 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001234 sys.exit(2)
1235
Doug Zongkereef39442009-04-02 12:14:19 -07001236 for o, a in opts:
1237 if o in ("-h", "--help"):
1238 Usage(docstring)
1239 sys.exit()
1240 elif o in ("-v", "--verbose"):
1241 OPTIONS.verbose = True
1242 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001243 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001244 elif o in ("--signapk_path",):
1245 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001246 elif o in ("--signapk_shared_library_path",):
1247 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001248 elif o in ("--extra_signapk_args",):
1249 OPTIONS.extra_signapk_args = shlex.split(a)
1250 elif o in ("--java_path",):
1251 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001252 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001253 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001254 elif o in ("--public_key_suffix",):
1255 OPTIONS.public_key_suffix = a
1256 elif o in ("--private_key_suffix",):
1257 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001258 elif o in ("--boot_signer_path",):
1259 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001260 elif o in ("--boot_signer_args",):
1261 OPTIONS.boot_signer_args = shlex.split(a)
1262 elif o in ("--verity_signer_path",):
1263 OPTIONS.verity_signer_path = a
1264 elif o in ("--verity_signer_args",):
1265 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001266 elif o in ("-s", "--device_specific"):
1267 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001268 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001269 key, value = a.split("=", 1)
1270 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001271 else:
1272 if extra_option_handler is None or not extra_option_handler(o, a):
1273 assert False, "unknown option \"%s\"" % (o,)
1274
Doug Zongker85448772014-09-09 14:59:20 -07001275 if OPTIONS.search_path:
1276 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1277 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001278
1279 return args
1280
1281
Tao Bao4c851b12016-09-19 13:54:38 -07001282def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001283 """Make a temp file and add it to the list of things to be deleted
1284 when Cleanup() is called. Return the filename."""
1285 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1286 os.close(fd)
1287 OPTIONS.tempfiles.append(fn)
1288 return fn
1289
1290
Tao Bao1c830bf2017-12-25 10:43:47 -08001291def MakeTempDir(prefix='tmp', suffix=''):
1292 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1293
1294 Returns:
1295 The absolute pathname of the new directory.
1296 """
1297 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1298 OPTIONS.tempfiles.append(dir_name)
1299 return dir_name
1300
1301
Doug Zongkereef39442009-04-02 12:14:19 -07001302def Cleanup():
1303 for i in OPTIONS.tempfiles:
1304 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001305 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001306 else:
1307 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001308 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001309
1310
1311class PasswordManager(object):
1312 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001313 self.editor = os.getenv("EDITOR")
1314 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001315
1316 def GetPasswords(self, items):
1317 """Get passwords corresponding to each string in 'items',
1318 returning a dict. (The dict may have keys in addition to the
1319 values in 'items'.)
1320
1321 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1322 user edit that file to add more needed passwords. If no editor is
1323 available, or $ANDROID_PW_FILE isn't define, prompts the user
1324 interactively in the ordinary way.
1325 """
1326
1327 current = self.ReadFile()
1328
1329 first = True
1330 while True:
1331 missing = []
1332 for i in items:
1333 if i not in current or not current[i]:
1334 missing.append(i)
1335 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001336 if not missing:
1337 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001338
1339 for i in missing:
1340 current[i] = ""
1341
1342 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001343 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001344 answer = raw_input("try to edit again? [y]> ").strip()
1345 if answer and answer[0] not in 'yY':
1346 raise RuntimeError("key passwords unavailable")
1347 first = False
1348
1349 current = self.UpdateAndReadFile(current)
1350
Dan Albert8b72aef2015-03-23 19:13:21 -07001351 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001352 """Prompt the user to enter a value (password) for each key in
1353 'current' whose value is fales. Returns a new dict with all the
1354 values.
1355 """
1356 result = {}
1357 for k, v in sorted(current.iteritems()):
1358 if v:
1359 result[k] = v
1360 else:
1361 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001362 result[k] = getpass.getpass(
1363 "Enter password for %s key> " % k).strip()
1364 if result[k]:
1365 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001366 return result
1367
1368 def UpdateAndReadFile(self, current):
1369 if not self.editor or not self.pwfile:
1370 return self.PromptResult(current)
1371
1372 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001373 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001374 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1375 f.write("# (Additional spaces are harmless.)\n\n")
1376
1377 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001378 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1379 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001380 f.write("[[[ %s ]]] %s\n" % (v, k))
1381 if not v and first_line is None:
1382 # position cursor on first line with no password.
1383 first_line = i + 4
1384 f.close()
1385
Tao Bao986ee862018-10-04 15:46:16 -07001386 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001387
1388 return self.ReadFile()
1389
1390 def ReadFile(self):
1391 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001392 if self.pwfile is None:
1393 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001394 try:
1395 f = open(self.pwfile, "r")
1396 for line in f:
1397 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001398 if not line or line[0] == '#':
1399 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001400 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1401 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001402 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001403 else:
1404 result[m.group(2)] = m.group(1)
1405 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001406 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001407 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001408 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001409 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001410
1411
Dan Albert8e0178d2015-01-27 15:53:15 -08001412def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1413 compress_type=None):
1414 import datetime
1415
1416 # http://b/18015246
1417 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1418 # for files larger than 2GiB. We can work around this by adjusting their
1419 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1420 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1421 # it isn't clear to me exactly what circumstances cause this).
1422 # `zipfile.write()` must be used directly to work around this.
1423 #
1424 # This mess can be avoided if we port to python3.
1425 saved_zip64_limit = zipfile.ZIP64_LIMIT
1426 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1427
1428 if compress_type is None:
1429 compress_type = zip_file.compression
1430 if arcname is None:
1431 arcname = filename
1432
1433 saved_stat = os.stat(filename)
1434
1435 try:
1436 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1437 # file to be zipped and reset it when we're done.
1438 os.chmod(filename, perms)
1439
1440 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001441 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1442 # intentional. zip stores datetimes in local time without a time zone
1443 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1444 # in the zip archive.
1445 local_epoch = datetime.datetime.fromtimestamp(0)
1446 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001447 os.utime(filename, (timestamp, timestamp))
1448
1449 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1450 finally:
1451 os.chmod(filename, saved_stat.st_mode)
1452 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1453 zipfile.ZIP64_LIMIT = saved_zip64_limit
1454
1455
Tao Bao58c1b962015-05-20 09:32:18 -07001456def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001457 compress_type=None):
1458 """Wrap zipfile.writestr() function to work around the zip64 limit.
1459
1460 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1461 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1462 when calling crc32(bytes).
1463
1464 But it still works fine to write a shorter string into a large zip file.
1465 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1466 when we know the string won't be too long.
1467 """
1468
1469 saved_zip64_limit = zipfile.ZIP64_LIMIT
1470 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1471
1472 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1473 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001474 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001475 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001476 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001477 else:
Tao Baof3282b42015-04-01 11:21:55 -07001478 zinfo = zinfo_or_arcname
1479
1480 # If compress_type is given, it overrides the value in zinfo.
1481 if compress_type is not None:
1482 zinfo.compress_type = compress_type
1483
Tao Bao58c1b962015-05-20 09:32:18 -07001484 # If perms is given, it has a priority.
1485 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001486 # If perms doesn't set the file type, mark it as a regular file.
1487 if perms & 0o770000 == 0:
1488 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001489 zinfo.external_attr = perms << 16
1490
Tao Baof3282b42015-04-01 11:21:55 -07001491 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001492 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1493
Dan Albert8b72aef2015-03-23 19:13:21 -07001494 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001495 zipfile.ZIP64_LIMIT = saved_zip64_limit
1496
1497
Tao Bao89d7ab22017-12-14 17:05:33 -08001498def ZipDelete(zip_filename, entries):
1499 """Deletes entries from a ZIP file.
1500
1501 Since deleting entries from a ZIP file is not supported, it shells out to
1502 'zip -d'.
1503
1504 Args:
1505 zip_filename: The name of the ZIP file.
1506 entries: The name of the entry, or the list of names to be deleted.
1507
1508 Raises:
1509 AssertionError: In case of non-zero return from 'zip'.
1510 """
1511 if isinstance(entries, basestring):
1512 entries = [entries]
1513 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001514 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001515
1516
Tao Baof3282b42015-04-01 11:21:55 -07001517def ZipClose(zip_file):
1518 # http://b/18015246
1519 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1520 # central directory.
1521 saved_zip64_limit = zipfile.ZIP64_LIMIT
1522 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1523
1524 zip_file.close()
1525
1526 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001527
1528
1529class DeviceSpecificParams(object):
1530 module = None
1531 def __init__(self, **kwargs):
1532 """Keyword arguments to the constructor become attributes of this
1533 object, which is passed to all functions in the device-specific
1534 module."""
1535 for k, v in kwargs.iteritems():
1536 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001537 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001538
1539 if self.module is None:
1540 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001541 if not path:
1542 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001543 try:
1544 if os.path.isdir(path):
1545 info = imp.find_module("releasetools", [path])
1546 else:
1547 d, f = os.path.split(path)
1548 b, x = os.path.splitext(f)
1549 if x == ".py":
1550 f = b
1551 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001552 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001553 self.module = imp.load_module("device_specific", *info)
1554 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001555 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001556
1557 def _DoCall(self, function_name, *args, **kwargs):
1558 """Call the named function in the device-specific module, passing
1559 the given args and kwargs. The first argument to the call will be
1560 the DeviceSpecific object itself. If there is no module, or the
1561 module does not define the function, return the value of the
1562 'default' kwarg (which itself defaults to None)."""
1563 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001564 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001565 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1566
1567 def FullOTA_Assertions(self):
1568 """Called after emitting the block of assertions at the top of a
1569 full OTA package. Implementations can add whatever additional
1570 assertions they like."""
1571 return self._DoCall("FullOTA_Assertions")
1572
Doug Zongkere5ff5902012-01-17 10:55:37 -08001573 def FullOTA_InstallBegin(self):
1574 """Called at the start of full OTA installation."""
1575 return self._DoCall("FullOTA_InstallBegin")
1576
Yifan Hong10c530d2018-12-27 17:34:18 -08001577 def FullOTA_GetBlockDifferences(self):
1578 """Called during full OTA installation and verification.
1579 Implementation should return a list of BlockDifference objects describing
1580 the update on each additional partitions.
1581 """
1582 return self._DoCall("FullOTA_GetBlockDifferences")
1583
Doug Zongker05d3dea2009-06-22 11:32:31 -07001584 def FullOTA_InstallEnd(self):
1585 """Called at the end of full OTA installation; typically this is
1586 used to install the image for the device's baseband processor."""
1587 return self._DoCall("FullOTA_InstallEnd")
1588
1589 def IncrementalOTA_Assertions(self):
1590 """Called after emitting the block of assertions at the top of an
1591 incremental OTA package. Implementations can add whatever
1592 additional assertions they like."""
1593 return self._DoCall("IncrementalOTA_Assertions")
1594
Doug Zongkere5ff5902012-01-17 10:55:37 -08001595 def IncrementalOTA_VerifyBegin(self):
1596 """Called at the start of the verification phase of incremental
1597 OTA installation; additional checks can be placed here to abort
1598 the script before any changes are made."""
1599 return self._DoCall("IncrementalOTA_VerifyBegin")
1600
Doug Zongker05d3dea2009-06-22 11:32:31 -07001601 def IncrementalOTA_VerifyEnd(self):
1602 """Called at the end of the verification phase of incremental OTA
1603 installation; additional checks can be placed here to abort the
1604 script before any changes are made."""
1605 return self._DoCall("IncrementalOTA_VerifyEnd")
1606
Doug Zongkere5ff5902012-01-17 10:55:37 -08001607 def IncrementalOTA_InstallBegin(self):
1608 """Called at the start of incremental OTA installation (after
1609 verification is complete)."""
1610 return self._DoCall("IncrementalOTA_InstallBegin")
1611
Yifan Hong10c530d2018-12-27 17:34:18 -08001612 def IncrementalOTA_GetBlockDifferences(self):
1613 """Called during incremental OTA installation and verification.
1614 Implementation should return a list of BlockDifference objects describing
1615 the update on each additional partitions.
1616 """
1617 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1618
Doug Zongker05d3dea2009-06-22 11:32:31 -07001619 def IncrementalOTA_InstallEnd(self):
1620 """Called at the end of incremental OTA installation; typically
1621 this is used to install the image for the device's baseband
1622 processor."""
1623 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001624
Tao Bao9bc6bb22015-11-09 16:58:28 -08001625 def VerifyOTA_Assertions(self):
1626 return self._DoCall("VerifyOTA_Assertions")
1627
Tao Bao76def242017-11-21 09:25:31 -08001628
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001629class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001630 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001631 self.name = name
1632 self.data = data
1633 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001634 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001635 self.sha1 = sha1(data).hexdigest()
1636
1637 @classmethod
1638 def FromLocalFile(cls, name, diskname):
1639 f = open(diskname, "rb")
1640 data = f.read()
1641 f.close()
1642 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001643
1644 def WriteToTemp(self):
1645 t = tempfile.NamedTemporaryFile()
1646 t.write(self.data)
1647 t.flush()
1648 return t
1649
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001650 def WriteToDir(self, d):
1651 with open(os.path.join(d, self.name), "wb") as fp:
1652 fp.write(self.data)
1653
Geremy Condra36bd3652014-02-06 19:45:10 -08001654 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001655 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001656
Tao Bao76def242017-11-21 09:25:31 -08001657
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001658DIFF_PROGRAM_BY_EXT = {
1659 ".gz" : "imgdiff",
1660 ".zip" : ["imgdiff", "-z"],
1661 ".jar" : ["imgdiff", "-z"],
1662 ".apk" : ["imgdiff", "-z"],
1663 ".img" : "imgdiff",
1664 }
1665
Tao Bao76def242017-11-21 09:25:31 -08001666
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001667class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001668 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001669 self.tf = tf
1670 self.sf = sf
1671 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001672 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001673
1674 def ComputePatch(self):
1675 """Compute the patch (as a string of data) needed to turn sf into
1676 tf. Returns the same tuple as GetPatch()."""
1677
1678 tf = self.tf
1679 sf = self.sf
1680
Doug Zongker24cd2802012-08-14 16:36:15 -07001681 if self.diff_program:
1682 diff_program = self.diff_program
1683 else:
1684 ext = os.path.splitext(tf.name)[1]
1685 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001686
1687 ttemp = tf.WriteToTemp()
1688 stemp = sf.WriteToTemp()
1689
1690 ext = os.path.splitext(tf.name)[1]
1691
1692 try:
1693 ptemp = tempfile.NamedTemporaryFile()
1694 if isinstance(diff_program, list):
1695 cmd = copy.copy(diff_program)
1696 else:
1697 cmd = [diff_program]
1698 cmd.append(stemp.name)
1699 cmd.append(ttemp.name)
1700 cmd.append(ptemp.name)
1701 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001702 err = []
1703 def run():
1704 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001705 if e:
1706 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001707 th = threading.Thread(target=run)
1708 th.start()
1709 th.join(timeout=300) # 5 mins
1710 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001711 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001712 p.terminate()
1713 th.join(5)
1714 if th.is_alive():
1715 p.kill()
1716 th.join()
1717
Tianjie Xua2a9f992018-01-05 15:15:54 -08001718 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001719 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001720 self.patch = None
1721 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001722 diff = ptemp.read()
1723 finally:
1724 ptemp.close()
1725 stemp.close()
1726 ttemp.close()
1727
1728 self.patch = diff
1729 return self.tf, self.sf, self.patch
1730
1731
1732 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001733 """Returns a tuple of (target_file, source_file, patch_data).
1734
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001735 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001736 computing the patch failed.
1737 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001738 return self.tf, self.sf, self.patch
1739
1740
1741def ComputeDifferences(diffs):
1742 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001743 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001744
1745 # Do the largest files first, to try and reduce the long-pole effect.
1746 by_size = [(i.tf.size, i) for i in diffs]
1747 by_size.sort(reverse=True)
1748 by_size = [i[1] for i in by_size]
1749
1750 lock = threading.Lock()
1751 diff_iter = iter(by_size) # accessed under lock
1752
1753 def worker():
1754 try:
1755 lock.acquire()
1756 for d in diff_iter:
1757 lock.release()
1758 start = time.time()
1759 d.ComputePatch()
1760 dur = time.time() - start
1761 lock.acquire()
1762
1763 tf, sf, patch = d.GetPatch()
1764 if sf.name == tf.name:
1765 name = tf.name
1766 else:
1767 name = "%s (%s)" % (tf.name, sf.name)
1768 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001769 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001770 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001771 logger.info(
1772 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1773 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001774 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001775 except Exception:
1776 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001777 raise
1778
1779 # start worker threads; wait for them all to finish.
1780 threads = [threading.Thread(target=worker)
1781 for i in range(OPTIONS.worker_threads)]
1782 for th in threads:
1783 th.start()
1784 while threads:
1785 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001786
1787
Dan Albert8b72aef2015-03-23 19:13:21 -07001788class BlockDifference(object):
1789 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001790 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001791 self.tgt = tgt
1792 self.src = src
1793 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001794 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001795 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001796
Tao Baodd2a5892015-03-12 12:32:37 -07001797 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001798 version = max(
1799 int(i) for i in
1800 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001801 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001802 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001803
1804 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001805 version=self.version,
1806 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001807 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001808 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001809 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001810 self.touched_src_ranges = b.touched_src_ranges
1811 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001812
Yifan Hong10c530d2018-12-27 17:34:18 -08001813 # On devices with dynamic partitions, for new partitions,
1814 # src is None but OPTIONS.source_info_dict is not.
1815 if OPTIONS.source_info_dict is None:
1816 is_dynamic_build = OPTIONS.info_dict.get(
1817 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001818 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001819 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001820 is_dynamic_build = OPTIONS.source_info_dict.get(
1821 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001822 is_dynamic_source = partition in shlex.split(
1823 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001824
Yifan Hongbb2658d2019-01-25 12:30:58 -08001825 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001826 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1827
Yifan Hongbb2658d2019-01-25 12:30:58 -08001828 # For dynamic partitions builds, check partition list in both source
1829 # and target build because new partitions may be added, and existing
1830 # partitions may be removed.
1831 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1832
Yifan Hong10c530d2018-12-27 17:34:18 -08001833 if is_dynamic:
1834 self.device = 'map_partition("%s")' % partition
1835 else:
1836 if OPTIONS.source_info_dict is None:
1837 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1838 else:
1839 _, device_path = GetTypeAndDevice("/" + partition,
1840 OPTIONS.source_info_dict)
1841 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001842
Tao Baod8d14be2016-02-04 14:26:02 -08001843 @property
1844 def required_cache(self):
1845 return self._required_cache
1846
Tao Bao76def242017-11-21 09:25:31 -08001847 def WriteScript(self, script, output_zip, progress=None,
1848 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001849 if not self.src:
1850 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001851 script.Print("Patching %s image unconditionally..." % (self.partition,))
1852 else:
1853 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001854
Dan Albert8b72aef2015-03-23 19:13:21 -07001855 if progress:
1856 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001857 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001858
1859 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001860 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001861
Tao Bao9bc6bb22015-11-09 16:58:28 -08001862 def WriteStrictVerifyScript(self, script):
1863 """Verify all the blocks in the care_map, including clobbered blocks.
1864
1865 This differs from the WriteVerifyScript() function: a) it prints different
1866 error messages; b) it doesn't allow half-way updated images to pass the
1867 verification."""
1868
1869 partition = self.partition
1870 script.Print("Verifying %s..." % (partition,))
1871 ranges = self.tgt.care_map
1872 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001873 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001874 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1875 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001876 self.device, ranges_str,
1877 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001878 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001879 script.AppendExtra("")
1880
Tao Baod522bdc2016-04-12 15:53:16 -07001881 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001882 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001883
1884 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001885 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001886 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001887
1888 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001889 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001890 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001891 ranges = self.touched_src_ranges
1892 expected_sha1 = self.touched_src_sha1
1893 else:
1894 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1895 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001896
1897 # No blocks to be checked, skipping.
1898 if not ranges:
1899 return
1900
Tao Bao5ece99d2015-05-12 11:42:31 -07001901 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001902 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001903 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001904 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1905 '"%s.patch.dat")) then' % (
1906 self.device, ranges_str, expected_sha1,
1907 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001908 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001909 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001910
Tianjie Xufc3422a2015-12-15 11:53:59 -08001911 if self.version >= 4:
1912
1913 # Bug: 21124327
1914 # When generating incrementals for the system and vendor partitions in
1915 # version 4 or newer, explicitly check the first block (which contains
1916 # the superblock) of the partition to see if it's what we expect. If
1917 # this check fails, give an explicit log message about the partition
1918 # having been remounted R/W (the most likely explanation).
1919 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08001920 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08001921
1922 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001923 if partition == "system":
1924 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1925 else:
1926 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001927 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08001928 'ifelse (block_image_recover({device}, "{ranges}") && '
1929 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001930 'package_extract_file("{partition}.transfer.list"), '
1931 '"{partition}.new.dat", "{partition}.patch.dat"), '
1932 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001933 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001934 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001935 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001936
Tao Baodd2a5892015-03-12 12:32:37 -07001937 # Abort the OTA update. Note that the incremental OTA cannot be applied
1938 # even if it may match the checksum of the target partition.
1939 # a) If version < 3, operations like move and erase will make changes
1940 # unconditionally and damage the partition.
1941 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001942 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001943 if partition == "system":
1944 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1945 else:
1946 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1947 script.AppendExtra((
1948 'abort("E%d: %s partition has unexpected contents");\n'
1949 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001950
Yifan Hong10c530d2018-12-27 17:34:18 -08001951 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07001952 partition = self.partition
1953 script.Print('Verifying the updated %s image...' % (partition,))
1954 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1955 ranges = self.tgt.care_map
1956 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001957 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001958 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001959 self.device, ranges_str,
1960 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001961
1962 # Bug: 20881595
1963 # Verify that extended blocks are really zeroed out.
1964 if self.tgt.extended:
1965 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001966 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001967 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001968 self.device, ranges_str,
1969 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001970 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001971 if partition == "system":
1972 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1973 else:
1974 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001975 script.AppendExtra(
1976 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001977 ' abort("E%d: %s partition has unexpected non-zero contents after '
1978 'OTA update");\n'
1979 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001980 else:
1981 script.Print('Verified the updated %s image.' % (partition,))
1982
Tianjie Xu209db462016-05-24 17:34:52 -07001983 if partition == "system":
1984 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1985 else:
1986 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1987
Tao Bao5fcaaef2015-06-01 13:40:49 -07001988 script.AppendExtra(
1989 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001990 ' abort("E%d: %s partition has unexpected contents after OTA '
1991 'update");\n'
1992 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001993
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001994 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001995 ZipWrite(output_zip,
1996 '{}.transfer.list'.format(self.path),
1997 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001998
Tao Bao76def242017-11-21 09:25:31 -08001999 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
2000 # its size. Quailty 9 almost triples the compression time but doesn't
2001 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002002 # zip | brotli(quality 6) | brotli(quality 9)
2003 # compressed_size: 942M | 869M (~8% reduced) | 854M
2004 # compression_time: 75s | 265s | 719s
2005 # decompression_time: 15s | 25s | 25s
2006
2007 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002008 brotli_cmd = ['brotli', '--quality=6',
2009 '--output={}.new.dat.br'.format(self.path),
2010 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002011 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002012 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002013
2014 new_data_name = '{}.new.dat.br'.format(self.partition)
2015 ZipWrite(output_zip,
2016 '{}.new.dat.br'.format(self.path),
2017 new_data_name,
2018 compress_type=zipfile.ZIP_STORED)
2019 else:
2020 new_data_name = '{}.new.dat'.format(self.partition)
2021 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2022
Dan Albert8e0178d2015-01-27 15:53:15 -08002023 ZipWrite(output_zip,
2024 '{}.patch.dat'.format(self.path),
2025 '{}.patch.dat'.format(self.partition),
2026 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002027
Tianjie Xu209db462016-05-24 17:34:52 -07002028 if self.partition == "system":
2029 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2030 else:
2031 code = ErrorCode.VENDOR_UPDATE_FAILURE
2032
Yifan Hong10c530d2018-12-27 17:34:18 -08002033 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002034 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002035 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002036 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002037 device=self.device, partition=self.partition,
2038 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002039 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002040
Dan Albert8b72aef2015-03-23 19:13:21 -07002041 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002042 data = source.ReadRangeSet(ranges)
2043 ctx = sha1()
2044
2045 for p in data:
2046 ctx.update(p)
2047
2048 return ctx.hexdigest()
2049
Tao Baoe9b61912015-07-09 17:37:49 -07002050 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2051 """Return the hash value for all zero blocks."""
2052 zero_block = '\x00' * 4096
2053 ctx = sha1()
2054 for _ in range(num_blocks):
2055 ctx.update(zero_block)
2056
2057 return ctx.hexdigest()
2058
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002059
2060DataImage = blockimgdiff.DataImage
2061
Tao Bao76def242017-11-21 09:25:31 -08002062
Doug Zongker96a57e72010-09-26 14:57:41 -07002063# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002064PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002065 "ext4": "EMMC",
2066 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002067 "f2fs": "EMMC",
2068 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002069}
Doug Zongker96a57e72010-09-26 14:57:41 -07002070
Tao Bao76def242017-11-21 09:25:31 -08002071
Doug Zongker96a57e72010-09-26 14:57:41 -07002072def GetTypeAndDevice(mount_point, info):
2073 fstab = info["fstab"]
2074 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002075 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2076 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002077 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002078 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002079
2080
2081def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002082 """Parses and converts a PEM-encoded certificate into DER-encoded.
2083
2084 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2085
2086 Returns:
2087 The decoded certificate string.
2088 """
2089 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002090 save = False
2091 for line in data.split("\n"):
2092 if "--END CERTIFICATE--" in line:
2093 break
2094 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002095 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002096 if "--BEGIN CERTIFICATE--" in line:
2097 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002098 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002099 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002100
Tao Bao04e1f012018-02-04 12:13:35 -08002101
2102def ExtractPublicKey(cert):
2103 """Extracts the public key (PEM-encoded) from the given certificate file.
2104
2105 Args:
2106 cert: The certificate filename.
2107
2108 Returns:
2109 The public key string.
2110
2111 Raises:
2112 AssertionError: On non-zero return from 'openssl'.
2113 """
2114 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2115 # While openssl 1.1 writes the key into the given filename followed by '-out',
2116 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2117 # stdout instead.
2118 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2119 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2120 pubkey, stderrdata = proc.communicate()
2121 assert proc.returncode == 0, \
2122 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2123 return pubkey
2124
2125
Doug Zongker412c02f2014-02-13 10:58:24 -08002126def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2127 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002128 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002129
Tao Bao6d5d6232018-03-09 17:04:42 -08002130 Most of the space in the boot and recovery images is just the kernel, which is
2131 identical for the two, so the resulting patch should be efficient. Add it to
2132 the output zip, along with a shell script that is run from init.rc on first
2133 boot to actually do the patching and install the new recovery image.
2134
2135 Args:
2136 input_dir: The top-level input directory of the target-files.zip.
2137 output_sink: The callback function that writes the result.
2138 recovery_img: File object for the recovery image.
2139 boot_img: File objects for the boot image.
2140 info_dict: A dict returned by common.LoadInfoDict() on the input
2141 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002142 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002143 if info_dict is None:
2144 info_dict = OPTIONS.info_dict
2145
Tao Bao6d5d6232018-03-09 17:04:42 -08002146 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002147
Tao Baof2cffbd2015-07-22 12:33:18 -07002148 if full_recovery_image:
2149 output_sink("etc/recovery.img", recovery_img.data)
2150
2151 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002152 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002153 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002154 # With system-root-image, boot and recovery images will have mismatching
2155 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2156 # to handle such a case.
2157 if system_root_image:
2158 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002159 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002160 assert not os.path.exists(path)
2161 else:
2162 diff_program = ["imgdiff"]
2163 if os.path.exists(path):
2164 diff_program.append("-b")
2165 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002166 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002167 else:
2168 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002169
2170 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2171 _, _, patch = d.ComputePatch()
2172 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002173
Dan Albertebb19aa2015-03-27 19:11:53 -07002174 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002175 # The following GetTypeAndDevice()s need to use the path in the target
2176 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002177 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2178 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2179 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002180 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002181
Tao Baof2cffbd2015-07-22 12:33:18 -07002182 if full_recovery_image:
2183 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002184if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2185 applypatch \\
2186 --flash /system/etc/recovery.img \\
2187 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2188 log -t recovery "Installing new recovery image: succeeded" || \\
2189 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002190else
2191 log -t recovery "Recovery image already installed"
2192fi
2193""" % {'type': recovery_type,
2194 'device': recovery_device,
2195 'sha1': recovery_img.sha1,
2196 'size': recovery_img.size}
2197 else:
2198 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002199if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2200 applypatch %(bonus_args)s \\
2201 --patch /system/recovery-from-boot.p \\
2202 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2203 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2204 log -t recovery "Installing new recovery image: succeeded" || \\
2205 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002206else
2207 log -t recovery "Recovery image already installed"
2208fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002209""" % {'boot_size': boot_img.size,
2210 'boot_sha1': boot_img.sha1,
2211 'recovery_size': recovery_img.size,
2212 'recovery_sha1': recovery_img.sha1,
2213 'boot_type': boot_type,
2214 'boot_device': boot_device,
2215 'recovery_type': recovery_type,
2216 'recovery_device': recovery_device,
2217 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002218
2219 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002220 # in the L release.
2221 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002222
Tao Bao32fcdab2018-10-12 10:30:39 -07002223 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002224
2225 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002226
2227
2228class DynamicPartitionUpdate(object):
2229 def __init__(self, src_group=None, tgt_group=None, progress=None,
2230 block_difference=None):
2231 self.src_group = src_group
2232 self.tgt_group = tgt_group
2233 self.progress = progress
2234 self.block_difference = block_difference
2235
2236 @property
2237 def src_size(self):
2238 if not self.block_difference:
2239 return 0
2240 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2241
2242 @property
2243 def tgt_size(self):
2244 if not self.block_difference:
2245 return 0
2246 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2247
2248 @staticmethod
2249 def _GetSparseImageSize(img):
2250 if not img:
2251 return 0
2252 return img.blocksize * img.total_blocks
2253
2254
2255class DynamicGroupUpdate(object):
2256 def __init__(self, src_size=None, tgt_size=None):
2257 # None: group does not exist. 0: no size limits.
2258 self.src_size = src_size
2259 self.tgt_size = tgt_size
2260
2261
2262class DynamicPartitionsDifference(object):
2263 def __init__(self, info_dict, block_diffs, progress_dict=None,
2264 source_info_dict=None):
2265 if progress_dict is None:
2266 progress_dict = dict()
2267
2268 self._remove_all_before_apply = False
2269 if source_info_dict is None:
2270 self._remove_all_before_apply = True
2271 source_info_dict = dict()
2272
2273 block_diff_dict = {e.partition:e for e in block_diffs}
2274 assert len(block_diff_dict) == len(block_diffs), \
2275 "Duplicated BlockDifference object for {}".format(
2276 [partition for partition, count in
2277 collections.Counter(e.partition for e in block_diffs).items()
2278 if count > 1])
2279
Yifan Hong79997e52019-01-23 16:56:19 -08002280 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002281
2282 for p, block_diff in block_diff_dict.items():
2283 self._partition_updates[p] = DynamicPartitionUpdate()
2284 self._partition_updates[p].block_difference = block_diff
2285
2286 for p, progress in progress_dict.items():
2287 if p in self._partition_updates:
2288 self._partition_updates[p].progress = progress
2289
2290 tgt_groups = shlex.split(info_dict.get(
2291 "super_partition_groups", "").strip())
2292 src_groups = shlex.split(source_info_dict.get(
2293 "super_partition_groups", "").strip())
2294
2295 for g in tgt_groups:
2296 for p in shlex.split(info_dict.get(
2297 "super_%s_partition_list" % g, "").strip()):
2298 assert p in self._partition_updates, \
2299 "{} is in target super_{}_partition_list but no BlockDifference " \
2300 "object is provided.".format(p, g)
2301 self._partition_updates[p].tgt_group = g
2302
2303 for g in src_groups:
2304 for p in shlex.split(source_info_dict.get(
2305 "super_%s_partition_list" % g, "").strip()):
2306 assert p in self._partition_updates, \
2307 "{} is in source super_{}_partition_list but no BlockDifference " \
2308 "object is provided.".format(p, g)
2309 self._partition_updates[p].src_group = g
2310
Yifan Hong45433e42019-01-18 13:55:25 -08002311 target_dynamic_partitions = set(shlex.split(info_dict.get(
2312 "dynamic_partition_list", "").strip()))
2313 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2314 if u.tgt_size)
2315 assert block_diffs_with_target == target_dynamic_partitions, \
2316 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2317 list(target_dynamic_partitions), list(block_diffs_with_target))
2318
2319 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2320 "dynamic_partition_list", "").strip()))
2321 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2322 if u.src_size)
2323 assert block_diffs_with_source == source_dynamic_partitions, \
2324 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2325 list(source_dynamic_partitions), list(block_diffs_with_source))
2326
Yifan Hong10c530d2018-12-27 17:34:18 -08002327 if self._partition_updates:
2328 logger.info("Updating dynamic partitions %s",
2329 self._partition_updates.keys())
2330
Yifan Hong79997e52019-01-23 16:56:19 -08002331 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002332
2333 for g in tgt_groups:
2334 self._group_updates[g] = DynamicGroupUpdate()
2335 self._group_updates[g].tgt_size = int(info_dict.get(
2336 "super_%s_group_size" % g, "0").strip())
2337
2338 for g in src_groups:
2339 if g not in self._group_updates:
2340 self._group_updates[g] = DynamicGroupUpdate()
2341 self._group_updates[g].src_size = int(source_info_dict.get(
2342 "super_%s_group_size" % g, "0").strip())
2343
2344 self._Compute()
2345
2346 def WriteScript(self, script, output_zip, write_verify_script=False):
2347 script.Comment('--- Start patching dynamic partitions ---')
2348 for p, u in self._partition_updates.items():
2349 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2350 script.Comment('Patch partition %s' % p)
2351 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2352 write_verify_script=False)
2353
2354 op_list_path = MakeTempFile()
2355 with open(op_list_path, 'w') as f:
2356 for line in self._op_list:
2357 f.write('{}\n'.format(line))
2358
2359 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2360
2361 script.Comment('Update dynamic partition metadata')
2362 script.AppendExtra('assert(update_dynamic_partitions('
2363 'package_extract_file("dynamic_partitions_op_list")));')
2364
2365 if write_verify_script:
2366 for p, u in self._partition_updates.items():
2367 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2368 u.block_difference.WritePostInstallVerifyScript(script)
2369 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2370
2371 for p, u in self._partition_updates.items():
2372 if u.tgt_size and u.src_size <= u.tgt_size:
2373 script.Comment('Patch partition %s' % p)
2374 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2375 write_verify_script=write_verify_script)
2376 if write_verify_script:
2377 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2378
2379 script.Comment('--- End patching dynamic partitions ---')
2380
2381 def _Compute(self):
2382 self._op_list = list()
2383
2384 def append(line):
2385 self._op_list.append(line)
2386
2387 def comment(line):
2388 self._op_list.append("# %s" % line)
2389
2390 if self._remove_all_before_apply:
2391 comment('Remove all existing dynamic partitions and groups before '
2392 'applying full OTA')
2393 append('remove_all_groups')
2394
2395 for p, u in self._partition_updates.items():
2396 if u.src_group and not u.tgt_group:
2397 append('remove %s' % p)
2398
2399 for p, u in self._partition_updates.items():
2400 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2401 comment('Move partition %s from %s to default' % (p, u.src_group))
2402 append('move %s default' % p)
2403
2404 for p, u in self._partition_updates.items():
2405 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2406 comment('Shrink partition %s from %d to %d' %
2407 (p, u.src_size, u.tgt_size))
2408 append('resize %s %s' % (p, u.tgt_size))
2409
2410 for g, u in self._group_updates.items():
2411 if u.src_size is not None and u.tgt_size is None:
2412 append('remove_group %s' % g)
2413 if (u.src_size is not None and u.tgt_size is not None and
2414 u.src_size > u.tgt_size):
2415 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2416 append('resize_group %s %d' % (g, u.tgt_size))
2417
2418 for g, u in self._group_updates.items():
2419 if u.src_size is None and u.tgt_size is not None:
2420 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2421 append('add_group %s %d' % (g, u.tgt_size))
2422 if (u.src_size is not None and u.tgt_size is not None and
2423 u.src_size < u.tgt_size):
2424 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2425 append('resize_group %s %d' % (g, u.tgt_size))
2426
2427 for p, u in self._partition_updates.items():
2428 if u.tgt_group and not u.src_group:
2429 comment('Add partition %s to group %s' % (p, u.tgt_group))
2430 append('add %s %s' % (p, u.tgt_group))
2431
2432 for p, u in self._partition_updates.items():
2433 if u.tgt_size and u.src_size < u.tgt_size:
2434 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2435 append('resize %s %d' % (p, u.tgt_size))
2436
2437 for p, u in self._partition_updates.items():
2438 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2439 comment('Move partition %s from default to %s' %
2440 (p, u.tgt_group))
2441 append('move %s %s' % (p, u.tgt_group))