blob: cfa81e1d1b3be89154509ad618bc0073fb408d57 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Tao Bao89fbb0f2017-01-10 10:47:58 -080015from __future__ import print_function
16
Yifan Hong10c530d2018-12-27 17:34:18 -080017import collections
Doug Zongkerea5d7a92010-09-12 15:26:16 -070018import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070019import errno
Doug Zongkereef39442009-04-02 12:14:19 -070020import getopt
21import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010022import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070023import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070024import json
25import logging
26import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070027import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080028import platform
Doug Zongkereef39442009-04-02 12:14:19 -070029import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070030import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070031import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080032import string
Doug Zongkereef39442009-04-02 12:14:19 -070033import subprocess
34import sys
35import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070036import threading
37import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070038import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080039from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070040
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070041import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080042import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070043
Tao Bao32fcdab2018-10-12 10:30:39 -070044logger = logging.getLogger(__name__)
45
Tao Bao986ee862018-10-04 15:46:16 -070046
Dan Albert8b72aef2015-03-23 19:13:21 -070047class Options(object):
48 def __init__(self):
49 platform_search_path = {
50 "linux2": "out/host/linux-x86",
51 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070052 }
Doug Zongker85448772014-09-09 14:59:20 -070053
Tao Bao76def242017-11-21 09:25:31 -080054 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070055 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080056 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070057 self.extra_signapk_args = []
58 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080059 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070060 self.public_key_suffix = ".x509.pem"
61 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070062 # use otatools built boot_signer by default
63 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070064 self.boot_signer_args = []
65 self.verity_signer_path = None
66 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070067 self.verbose = False
68 self.tempfiles = []
69 self.device_specific = None
70 self.extras = {}
71 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070072 self.source_info_dict = None
73 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070074 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070075 # Stash size cannot exceed cache_size * threshold.
76 self.cache_size = None
77 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070078
79
80OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070081
Tao Bao71197512018-10-11 14:08:45 -070082# The block size that's used across the releasetools scripts.
83BLOCK_SIZE = 4096
84
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080085# Values for "certificate" in apkcerts that mean special things.
86SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
87
Tao Bao9dd909e2017-11-14 11:27:32 -080088# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010089AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010090 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080091
Tianjie Xu861f4132018-09-12 11:49:33 -070092# Partitions that should have their care_map added to META/care_map.pb
93PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
94 'odm')
95
96
Tianjie Xu209db462016-05-24 17:34:52 -070097class ErrorCode(object):
98 """Define error_codes for failures that happen during the actual
99 update package installation.
100
101 Error codes 0-999 are reserved for failures before the package
102 installation (i.e. low battery, package verification failure).
103 Detailed code in 'bootable/recovery/error_code.h' """
104
105 SYSTEM_VERIFICATION_FAILURE = 1000
106 SYSTEM_UPDATE_FAILURE = 1001
107 SYSTEM_UNEXPECTED_CONTENTS = 1002
108 SYSTEM_NONZERO_CONTENTS = 1003
109 SYSTEM_RECOVER_FAILURE = 1004
110 VENDOR_VERIFICATION_FAILURE = 2000
111 VENDOR_UPDATE_FAILURE = 2001
112 VENDOR_UNEXPECTED_CONTENTS = 2002
113 VENDOR_NONZERO_CONTENTS = 2003
114 VENDOR_RECOVER_FAILURE = 2004
115 OEM_PROP_MISMATCH = 3000
116 FINGERPRINT_MISMATCH = 3001
117 THUMBPRINT_MISMATCH = 3002
118 OLDER_BUILD = 3003
119 DEVICE_MISMATCH = 3004
120 BAD_PATCH_FILE = 3005
121 INSUFFICIENT_CACHE_SPACE = 3006
122 TUNE_PARTITION_FAILURE = 3007
123 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800124
Tao Bao80921982018-03-21 21:02:19 -0700125
Dan Albert8b72aef2015-03-23 19:13:21 -0700126class ExternalError(RuntimeError):
127 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700128
129
Tao Bao32fcdab2018-10-12 10:30:39 -0700130def InitLogging():
131 DEFAULT_LOGGING_CONFIG = {
132 'version': 1,
133 'disable_existing_loggers': False,
134 'formatters': {
135 'standard': {
136 'format':
137 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
138 'datefmt': '%Y-%m-%d %H:%M:%S',
139 },
140 },
141 'handlers': {
142 'default': {
143 'class': 'logging.StreamHandler',
144 'formatter': 'standard',
145 },
146 },
147 'loggers': {
148 '': {
149 'handlers': ['default'],
150 'level': 'WARNING',
151 'propagate': True,
152 }
153 }
154 }
155 env_config = os.getenv('LOGGING_CONFIG')
156 if env_config:
157 with open(env_config) as f:
158 config = json.load(f)
159 else:
160 config = DEFAULT_LOGGING_CONFIG
161
162 # Increase the logging level for verbose mode.
163 if OPTIONS.verbose:
164 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
165 config['loggers']['']['level'] = 'INFO'
166
167 logging.config.dictConfig(config)
168
169
Tao Bao39451582017-05-04 11:10:47 -0700170def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700171 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700172
Tao Bao73dd4f42018-10-04 16:25:33 -0700173 Args:
174 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700175 verbose: Whether the commands should be shown. Default to the global
176 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700177 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
178 stdin, etc. stdout and stderr will default to subprocess.PIPE and
179 subprocess.STDOUT respectively unless caller specifies any of them.
180
181 Returns:
182 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700183 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700184 if 'stdout' not in kwargs and 'stderr' not in kwargs:
185 kwargs['stdout'] = subprocess.PIPE
186 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700187 # Don't log any if caller explicitly says so.
188 if verbose != False:
189 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700190 return subprocess.Popen(args, **kwargs)
191
192
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800193def RunAndWait(args, verbose=None, **kwargs):
Bill Peckham889b0c62019-02-21 18:53:37 -0800194 """Runs the given command waiting for it to complete.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800195
196 Args:
197 args: The command represented as a list of strings.
198 verbose: Whether the commands should be shown. Default to the global
199 verbosity if unspecified.
200 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
201 stdin, etc. stdout and stderr will default to subprocess.PIPE and
202 subprocess.STDOUT respectively unless caller specifies any of them.
203
Bill Peckham889b0c62019-02-21 18:53:37 -0800204 Raises:
205 ExternalError: On non-zero exit from the command.
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800206 """
207 proc = Run(args, verbose=verbose, **kwargs)
208 proc.wait()
Bill Peckham889b0c62019-02-21 18:53:37 -0800209
210 if proc.returncode != 0:
211 raise ExternalError(
212 "Failed to run command '{}' (exit code {})".format(
213 args, proc.returncode))
Bill Peckhame9eb5f92019-02-01 15:52:10 -0800214
215
Tao Bao986ee862018-10-04 15:46:16 -0700216def RunAndCheckOutput(args, verbose=None, **kwargs):
217 """Runs the given command and returns the output.
218
219 Args:
220 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700221 verbose: Whether the commands should be shown. Default to the global
222 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700223 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
224 stdin, etc. stdout and stderr will default to subprocess.PIPE and
225 subprocess.STDOUT respectively unless caller specifies any of them.
226
227 Returns:
228 The output string.
229
230 Raises:
231 ExternalError: On non-zero exit from the command.
232 """
Tao Bao986ee862018-10-04 15:46:16 -0700233 proc = Run(args, verbose=verbose, **kwargs)
234 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700235 # Don't log any if caller explicitly says so.
236 if verbose != False:
237 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700238 if proc.returncode != 0:
239 raise ExternalError(
240 "Failed to run command '{}' (exit code {}):\n{}".format(
241 args, proc.returncode, output))
242 return output
243
244
Tao Baoc765cca2018-01-31 17:32:40 -0800245def RoundUpTo4K(value):
246 rounded_up = value + 4095
247 return rounded_up - (rounded_up % 4096)
248
249
Ying Wang7e6d4e42010-12-13 16:25:36 -0800250def CloseInheritedPipes():
251 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
252 before doing other work."""
253 if platform.system() != "Darwin":
254 return
255 for d in range(3, 1025):
256 try:
257 stat = os.fstat(d)
258 if stat is not None:
259 pipebit = stat[0] & 0x1000
260 if pipebit != 0:
261 os.close(d)
262 except OSError:
263 pass
264
265
Tao Bao410ad8b2018-08-24 12:08:38 -0700266def LoadInfoDict(input_file, repacking=False):
267 """Loads the key/value pairs from the given input target_files.
268
269 It reads `META/misc_info.txt` file in the target_files input, does sanity
270 checks and returns the parsed key/value pairs for to the given build. It's
271 usually called early when working on input target_files files, e.g. when
272 generating OTAs, or signing builds. Note that the function may be called
273 against an old target_files file (i.e. from past dessert releases). So the
274 property parsing needs to be backward compatible.
275
276 In a `META/misc_info.txt`, a few properties are stored as links to the files
277 in the PRODUCT_OUT directory. It works fine with the build system. However,
278 they are no longer available when (re)generating images from target_files zip.
279 When `repacking` is True, redirect these properties to the actual files in the
280 unzipped directory.
281
282 Args:
283 input_file: The input target_files file, which could be an open
284 zipfile.ZipFile instance, or a str for the dir that contains the files
285 unzipped from a target_files file.
286 repacking: Whether it's trying repack an target_files file after loading the
287 info dict (default: False). If so, it will rewrite a few loaded
288 properties (e.g. selinux_fc, root_dir) to point to the actual files in
289 target_files file. When doing repacking, `input_file` must be a dir.
290
291 Returns:
292 A dict that contains the parsed key/value pairs.
293
294 Raises:
295 AssertionError: On invalid input arguments.
296 ValueError: On malformed input values.
297 """
298 if repacking:
299 assert isinstance(input_file, str), \
300 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700301
Doug Zongkerc9253822014-02-04 12:17:58 -0800302 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700303 if isinstance(input_file, zipfile.ZipFile):
304 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800305 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700306 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800307 try:
308 with open(path) as f:
309 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700310 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800311 if e.errno == errno.ENOENT:
312 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800313
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700314 try:
Michael Runge6e836112014-04-15 17:40:21 -0700315 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700316 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700317 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700318
Tao Bao410ad8b2018-08-24 12:08:38 -0700319 if "recovery_api_version" not in d:
320 raise ValueError("Failed to find 'recovery_api_version'")
321 if "fstab_version" not in d:
322 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800323
Tao Bao410ad8b2018-08-24 12:08:38 -0700324 if repacking:
325 # We carry a copy of file_contexts.bin under META/. If not available, search
326 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
327 # images than the one running on device, in that case, we must have the one
328 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700329 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700330 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700331 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700332
Tom Cherryd14b8952018-08-09 14:26:00 -0700333 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700334
Tom Cherryd14b8952018-08-09 14:26:00 -0700335 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700336 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700337 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700338 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700339
Tao Baof54216f2016-03-29 15:12:37 -0700340 # Redirect {system,vendor}_base_fs_file.
341 if "system_base_fs_file" in d:
342 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700343 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700344 if os.path.exists(system_base_fs_file):
345 d["system_base_fs_file"] = system_base_fs_file
346 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700347 logger.warning(
348 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700349 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700350
351 if "vendor_base_fs_file" in d:
352 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700353 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700354 if os.path.exists(vendor_base_fs_file):
355 d["vendor_base_fs_file"] = vendor_base_fs_file
356 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700357 logger.warning(
358 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700359 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700360
Doug Zongker37974732010-09-16 17:44:38 -0700361 def makeint(key):
362 if key in d:
363 d[key] = int(d[key], 0)
364
365 makeint("recovery_api_version")
366 makeint("blocksize")
367 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700368 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700369 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700370 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700371 makeint("recovery_size")
372 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800373 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700374
Tao Baoa57ab9f2018-08-24 12:08:38 -0700375 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
376 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
377 # cases, since it may load the info_dict from an old build (e.g. when
378 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800379 system_root_image = d.get("system_root_image") == "true"
380 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700381 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700382 if isinstance(input_file, zipfile.ZipFile):
383 if recovery_fstab_path not in input_file.namelist():
384 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
385 else:
386 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
387 if not os.path.exists(path):
388 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800389 d["fstab"] = LoadRecoveryFSTab(
390 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700391
Tao Bao76def242017-11-21 09:25:31 -0800392 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700393 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700394 if isinstance(input_file, zipfile.ZipFile):
395 if recovery_fstab_path not in input_file.namelist():
396 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
397 else:
398 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
399 if not os.path.exists(path):
400 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800401 d["fstab"] = LoadRecoveryFSTab(
402 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700403
Tianjie Xucfa86222016-03-07 16:31:19 -0800404 else:
405 d["fstab"] = None
406
Tianjie Xu861f4132018-09-12 11:49:33 -0700407 # Tries to load the build props for all partitions with care_map, including
408 # system and vendor.
409 for partition in PARTITIONS_WITH_CARE_MAP:
410 d["{}.build.prop".format(partition)] = LoadBuildProp(
411 read_helper, "{}/build.prop".format(partition.upper()))
412 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800413
414 # Set up the salt (based on fingerprint or thumbprint) that will be used when
415 # adding AVB footer.
416 if d.get("avb_enable") == "true":
417 fp = None
418 if "build.prop" in d:
419 build_prop = d["build.prop"]
420 if "ro.build.fingerprint" in build_prop:
421 fp = build_prop["ro.build.fingerprint"]
422 elif "ro.build.thumbprint" in build_prop:
423 fp = build_prop["ro.build.thumbprint"]
424 if fp:
425 d["avb_salt"] = sha256(fp).hexdigest()
426
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700427 return d
428
Tao Baod1de6f32017-03-01 16:38:48 -0800429
Tao Baobcd1d162017-08-26 13:10:26 -0700430def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700431 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700432 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700433 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700434 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700435 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700436 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700437
Tao Baod1de6f32017-03-01 16:38:48 -0800438
Michael Runge6e836112014-04-15 17:40:21 -0700439def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700440 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700441 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700442 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700443 if not line or line.startswith("#"):
444 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700445 if "=" in line:
446 name, value = line.split("=", 1)
447 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700448 return d
449
Tao Baod1de6f32017-03-01 16:38:48 -0800450
Tianjie Xucfa86222016-03-07 16:31:19 -0800451def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
452 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700453 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800454 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700455 self.mount_point = mount_point
456 self.fs_type = fs_type
457 self.device = device
458 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700459 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700460
461 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800462 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700463 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700464 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700465 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700466
Tao Baod1de6f32017-03-01 16:38:48 -0800467 assert fstab_version == 2
468
469 d = {}
470 for line in data.split("\n"):
471 line = line.strip()
472 if not line or line.startswith("#"):
473 continue
474
475 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
476 pieces = line.split()
477 if len(pieces) != 5:
478 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
479
480 # Ignore entries that are managed by vold.
481 options = pieces[4]
482 if "voldmanaged=" in options:
483 continue
484
485 # It's a good line, parse it.
486 length = 0
487 options = options.split(",")
488 for i in options:
489 if i.startswith("length="):
490 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800491 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800492 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700493 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800494
Tao Baod1de6f32017-03-01 16:38:48 -0800495 mount_flags = pieces[3]
496 # Honor the SELinux context if present.
497 context = None
498 for i in mount_flags.split(","):
499 if i.startswith("context="):
500 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800501
Tao Baod1de6f32017-03-01 16:38:48 -0800502 mount_point = pieces[1]
503 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
504 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800505
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700506 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700507 # system. Other areas assume system is always at "/system" so point /system
508 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700509 if system_root_image:
510 assert not d.has_key("/system") and d.has_key("/")
511 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700512 return d
513
514
Doug Zongker37974732010-09-16 17:44:38 -0700515def DumpInfoDict(d):
516 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700517 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700518
Dan Albert8b72aef2015-03-23 19:13:21 -0700519
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800520def AppendAVBSigningArgs(cmd, partition):
521 """Append signing arguments for avbtool."""
522 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
523 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
524 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
525 if key_path and algorithm:
526 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700527 avb_salt = OPTIONS.info_dict.get("avb_salt")
528 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700529 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700530 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800531
532
Tao Bao02a08592018-07-22 12:40:45 -0700533def GetAvbChainedPartitionArg(partition, info_dict, key=None):
534 """Constructs and returns the arg to build or verify a chained partition.
535
536 Args:
537 partition: The partition name.
538 info_dict: The info dict to look up the key info and rollback index
539 location.
540 key: The key to be used for building or verifying the partition. Defaults to
541 the key listed in info_dict.
542
543 Returns:
544 A string of form "partition:rollback_index_location:key" that can be used to
545 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700546 """
547 if key is None:
548 key = info_dict["avb_" + partition + "_key_path"]
549 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
550 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
Tao Bao986ee862018-10-04 15:46:16 -0700551 RunAndCheckOutput(
Tao Bao73dd4f42018-10-04 16:25:33 -0700552 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path])
Tao Bao02a08592018-07-22 12:40:45 -0700553
554 rollback_index_location = info_dict[
555 "avb_" + partition + "_rollback_index_location"]
556 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
557
558
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700559def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800560 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700561 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700562
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700563 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800564 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
565 we are building a two-step special image (i.e. building a recovery image to
566 be loaded into /boot in two-step OTAs).
567
568 Return the image data, or None if sourcedir does not appear to contains files
569 for building the requested image.
570 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700571
572 def make_ramdisk():
573 ramdisk_img = tempfile.NamedTemporaryFile()
574
575 if os.access(fs_config_file, os.F_OK):
576 cmd = ["mkbootfs", "-f", fs_config_file,
577 os.path.join(sourcedir, "RAMDISK")]
578 else:
579 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
580 p1 = Run(cmd, stdout=subprocess.PIPE)
581 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
582
583 p2.wait()
584 p1.wait()
585 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
586 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
587
588 return ramdisk_img
589
590 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
591 return None
592
593 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700594 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700595
Doug Zongkerd5131602012-08-02 14:46:42 -0700596 if info_dict is None:
597 info_dict = OPTIONS.info_dict
598
Doug Zongkereef39442009-04-02 12:14:19 -0700599 img = tempfile.NamedTemporaryFile()
600
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700601 if has_ramdisk:
602 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700603
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800604 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
605 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
606
607 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700608
Benoit Fradina45a8682014-07-14 21:00:43 +0200609 fn = os.path.join(sourcedir, "second")
610 if os.access(fn, os.F_OK):
611 cmd.append("--second")
612 cmd.append(fn)
613
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800614 fn = os.path.join(sourcedir, "dtb")
615 if os.access(fn, os.F_OK):
616 cmd.append("--dtb")
617 cmd.append(fn)
618
Doug Zongker171f1cd2009-06-15 22:36:37 -0700619 fn = os.path.join(sourcedir, "cmdline")
620 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700621 cmd.append("--cmdline")
622 cmd.append(open(fn).read().rstrip("\n"))
623
624 fn = os.path.join(sourcedir, "base")
625 if os.access(fn, os.F_OK):
626 cmd.append("--base")
627 cmd.append(open(fn).read().rstrip("\n"))
628
Ying Wang4de6b5b2010-08-25 14:29:34 -0700629 fn = os.path.join(sourcedir, "pagesize")
630 if os.access(fn, os.F_OK):
631 cmd.append("--pagesize")
632 cmd.append(open(fn).read().rstrip("\n"))
633
Tao Bao76def242017-11-21 09:25:31 -0800634 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700635 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700636 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700637
Tao Bao76def242017-11-21 09:25:31 -0800638 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000639 if args and args.strip():
640 cmd.extend(shlex.split(args))
641
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700642 if has_ramdisk:
643 cmd.extend(["--ramdisk", ramdisk_img.name])
644
Tao Baod95e9fd2015-03-29 23:07:41 -0700645 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800646 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700647 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700648 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700649 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700650 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700651
Tao Baobf70c3182017-07-11 17:27:55 -0700652 # "boot" or "recovery", without extension.
653 partition_name = os.path.basename(sourcedir).lower()
654
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800655 if partition_name == "recovery":
656 if info_dict.get("include_recovery_dtbo") == "true":
657 fn = os.path.join(sourcedir, "recovery_dtbo")
658 cmd.extend(["--recovery_dtbo", fn])
659 if info_dict.get("include_recovery_acpio") == "true":
660 fn = os.path.join(sourcedir, "recovery_acpio")
661 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700662
Tao Bao986ee862018-10-04 15:46:16 -0700663 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700664
Tao Bao76def242017-11-21 09:25:31 -0800665 if (info_dict.get("boot_signer") == "true" and
666 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800667 # Hard-code the path as "/boot" for two-step special recovery image (which
668 # will be loaded into /boot during the two-step OTA).
669 if two_step_image:
670 path = "/boot"
671 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700672 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700673 cmd = [OPTIONS.boot_signer_path]
674 cmd.extend(OPTIONS.boot_signer_args)
675 cmd.extend([path, img.name,
676 info_dict["verity_key"] + ".pk8",
677 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700678 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700679
Tao Baod95e9fd2015-03-29 23:07:41 -0700680 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800681 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700682 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700683 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800684 # We have switched from the prebuilt futility binary to using the tool
685 # (futility-host) built from the source. Override the setting in the old
686 # TF.zip.
687 futility = info_dict["futility"]
688 if futility.startswith("prebuilts/"):
689 futility = "futility-host"
690 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700691 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700692 info_dict["vboot_key"] + ".vbprivk",
693 info_dict["vboot_subkey"] + ".vbprivk",
694 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700695 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700696 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700697
Tao Baof3282b42015-04-01 11:21:55 -0700698 # Clean up the temp files.
699 img_unsigned.close()
700 img_keyblock.close()
701
David Zeuthen8fecb282017-12-01 16:24:01 -0500702 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800703 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700704 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500705 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400706 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700707 "--partition_size", str(part_size), "--partition_name",
708 partition_name]
709 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500710 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400711 if args and args.strip():
712 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700713 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500714
715 img.seek(os.SEEK_SET, 0)
716 data = img.read()
717
718 if has_ramdisk:
719 ramdisk_img.close()
720 img.close()
721
722 return data
723
724
Doug Zongkerd5131602012-08-02 14:46:42 -0700725def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800726 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700727 """Return a File object with the desired bootable image.
728
729 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
730 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
731 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700732
Doug Zongker55d93282011-01-25 17:03:34 -0800733 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
734 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700735 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800736 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700737
738 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
739 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700740 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700741 return File.FromLocalFile(name, prebuilt_path)
742
Tao Bao32fcdab2018-10-12 10:30:39 -0700743 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700744
745 if info_dict is None:
746 info_dict = OPTIONS.info_dict
747
748 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800749 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
750 # for recovery.
751 has_ramdisk = (info_dict.get("system_root_image") != "true" or
752 prebuilt_name != "boot.img" or
753 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700754
Doug Zongker6f1d0312014-08-22 08:07:12 -0700755 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400756 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
757 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800758 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700759 if data:
760 return File(name, data)
761 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800762
Doug Zongkereef39442009-04-02 12:14:19 -0700763
Narayan Kamatha07bf042017-08-14 14:49:21 +0100764def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800765 """Gunzips the given gzip compressed file to a given output file."""
766 with gzip.open(in_filename, "rb") as in_file, \
767 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100768 shutil.copyfileobj(in_file, out_file)
769
770
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800771def UnzipToDir(filename, dirname, pattern=None):
772 """Unzips the archive to the given directory.
773
774 Args:
775 filename: The name of the zip file to unzip.
776
777 dirname: Where the unziped files will land.
778
779 pattern: Files to unzip from the archive. If omitted, will unzip the entire
780 archvie.
781 """
782
783 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
784 if pattern is not None:
785 cmd.extend(pattern)
786 RunAndCheckOutput(cmd)
787
788
Doug Zongker75f17362009-12-08 13:46:44 -0800789def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800790 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800791
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800792 Args:
793 filename: If filename is of the form "foo.zip+bar.zip", unzip foo.zip into
794 a temp dir, then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
795
796 pattern: Files to unzip from the archive. If omitted, will unzip the entire
797 archvie.
Doug Zongker55d93282011-01-25 17:03:34 -0800798
Tao Bao1c830bf2017-12-25 10:43:47 -0800799 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800800 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800801 """
Doug Zongkereef39442009-04-02 12:14:19 -0700802
Tao Bao1c830bf2017-12-25 10:43:47 -0800803 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800804 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
805 if m:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800806 UnzipToDir(m.group(1), tmp, pattern)
807 UnzipToDir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"), pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800808 filename = m.group(1)
809 else:
Bill Peckham8ff3fbd2019-02-22 10:57:43 -0800810 UnzipToDir(filename, tmp, pattern)
Doug Zongker55d93282011-01-25 17:03:34 -0800811
Tao Baodba59ee2018-01-09 13:21:02 -0800812 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700813
814
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700815def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
816 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800817 """Returns a SparseImage object suitable for passing to BlockImageDiff.
818
819 This function loads the specified sparse image from the given path, and
820 performs additional processing for OTA purpose. For example, it always adds
821 block 0 to clobbered blocks list. It also detects files that cannot be
822 reconstructed from the block list, for whom we should avoid applying imgdiff.
823
824 Args:
825 which: The partition name, which must be "system" or "vendor".
826 tmpdir: The directory that contains the prebuilt image and block map file.
827 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800828 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700829 hashtree_info_generator: If present, generates the hashtree_info for this
830 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800831 Returns:
832 A SparseImage object, with file_map info loaded.
833 """
834 assert which in ("system", "vendor")
835
836 path = os.path.join(tmpdir, "IMAGES", which + ".img")
837 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
838
839 # The image and map files must have been created prior to calling
840 # ota_from_target_files.py (since LMP).
841 assert os.path.exists(path) and os.path.exists(mappath)
842
843 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
844 # it to clobbered_blocks so that it will be written to the target
845 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
846 clobbered_blocks = "0"
847
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700848 image = sparse_img.SparseImage(
849 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
850 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800851
852 # block.map may contain less blocks, because mke2fs may skip allocating blocks
853 # if they contain all zeros. We can't reconstruct such a file from its block
854 # list. Tag such entries accordingly. (Bug: 65213616)
855 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800856 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700857 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800858 continue
859
Tom Cherryd14b8952018-08-09 14:26:00 -0700860 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
861 # filename listed in system.map may contain an additional leading slash
862 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
863 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700864 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
865
Tom Cherryd14b8952018-08-09 14:26:00 -0700866 # Special handling another case, where files not under /system
867 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700868 if which == 'system' and not arcname.startswith('SYSTEM'):
869 arcname = 'ROOT/' + arcname
870
871 assert arcname in input_zip.namelist(), \
872 "Failed to find the ZIP entry for {}".format(entry)
873
Tao Baoc765cca2018-01-31 17:32:40 -0800874 info = input_zip.getinfo(arcname)
875 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800876
877 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800878 # image, check the original block list to determine its completeness. Note
879 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800880 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800881 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800882
Tao Baoc765cca2018-01-31 17:32:40 -0800883 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
884 ranges.extra['incomplete'] = True
885
886 return image
887
888
Doug Zongkereef39442009-04-02 12:14:19 -0700889def GetKeyPasswords(keylist):
890 """Given a list of keys, prompt the user to enter passwords for
891 those which require them. Return a {key: password} dict. password
892 will be None if the key has no password."""
893
Doug Zongker8ce7c252009-05-22 13:34:54 -0700894 no_passwords = []
895 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700896 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700897 devnull = open("/dev/null", "w+b")
898 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800899 # We don't need a password for things that aren't really keys.
900 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700901 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700902 continue
903
T.R. Fullhart37e10522013-03-18 10:31:26 -0700904 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700905 "-inform", "DER", "-nocrypt"],
906 stdin=devnull.fileno(),
907 stdout=devnull.fileno(),
908 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700909 p.communicate()
910 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700911 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700912 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700913 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700914 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
915 "-inform", "DER", "-passin", "pass:"],
916 stdin=devnull.fileno(),
917 stdout=devnull.fileno(),
918 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700919 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700920 if p.returncode == 0:
921 # Encrypted key with empty string as password.
922 key_passwords[k] = ''
923 elif stderr.startswith('Error decrypting key'):
924 # Definitely encrypted key.
925 # It would have said "Error reading key" if it didn't parse correctly.
926 need_passwords.append(k)
927 else:
928 # Potentially, a type of key that openssl doesn't understand.
929 # We'll let the routines in signapk.jar handle it.
930 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700931 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700932
T.R. Fullhart37e10522013-03-18 10:31:26 -0700933 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800934 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700935 return key_passwords
936
937
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800938def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700939 """Gets the minSdkVersion declared in the APK.
940
941 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
942 This can be both a decimal number (API Level) or a codename.
943
944 Args:
945 apk_name: The APK filename.
946
947 Returns:
948 The parsed SDK version string.
949
950 Raises:
951 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800952 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700953 proc = Run(
954 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
955 stderr=subprocess.PIPE)
956 stdoutdata, stderrdata = proc.communicate()
957 if proc.returncode != 0:
958 raise ExternalError(
959 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
960 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800961
Tao Baof47bf0f2018-03-21 23:28:51 -0700962 for line in stdoutdata.split("\n"):
963 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800964 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
965 if m:
966 return m.group(1)
967 raise ExternalError("No minSdkVersion returned by aapt")
968
969
970def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700971 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800972
Tao Baof47bf0f2018-03-21 23:28:51 -0700973 If minSdkVersion is set to a codename, it is translated to a number using the
974 provided map.
975
976 Args:
977 apk_name: The APK filename.
978
979 Returns:
980 The parsed SDK version number.
981
982 Raises:
983 ExternalError: On failing to get the min SDK version number.
984 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800985 version = GetMinSdkVersion(apk_name)
986 try:
987 return int(version)
988 except ValueError:
989 # Not a decimal number. Codename?
990 if version in codename_to_api_level_map:
991 return codename_to_api_level_map[version]
992 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700993 raise ExternalError(
994 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
995 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800996
997
998def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800999 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -07001000 """Sign the input_name zip/jar/apk, producing output_name. Use the
1001 given key and password (the latter may be None if the key does not
1002 have a password.
1003
Doug Zongker951495f2009-08-14 12:44:19 -07001004 If whole_file is true, use the "-w" option to SignApk to embed a
1005 signature that covers the whole file in the archive comment of the
1006 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001007
1008 min_api_level is the API Level (int) of the oldest platform this file may end
1009 up on. If not specified for an APK, the API Level is obtained by interpreting
1010 the minSdkVersion attribute of the APK's AndroidManifest.xml.
1011
1012 codename_to_api_level_map is needed to translate the codename which may be
1013 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -07001014 """
Tao Bao76def242017-11-21 09:25:31 -08001015 if codename_to_api_level_map is None:
1016 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -07001017
Alex Klyubin9667b182015-12-10 13:38:50 -08001018 java_library_path = os.path.join(
1019 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1020
Tao Baoe95540e2016-11-08 12:08:53 -08001021 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1022 ["-Djava.library.path=" + java_library_path,
1023 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
1024 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001025 if whole_file:
1026 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001027
1028 min_sdk_version = min_api_level
1029 if min_sdk_version is None:
1030 if not whole_file:
1031 min_sdk_version = GetMinSdkVersionInt(
1032 input_name, codename_to_api_level_map)
1033 if min_sdk_version is not None:
1034 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1035
T.R. Fullhart37e10522013-03-18 10:31:26 -07001036 cmd.extend([key + OPTIONS.public_key_suffix,
1037 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001038 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001039
Tao Bao73dd4f42018-10-04 16:25:33 -07001040 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001041 if password is not None:
1042 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001043 stdoutdata, _ = proc.communicate(password)
1044 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001045 raise ExternalError(
1046 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001047 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001048
Doug Zongkereef39442009-04-02 12:14:19 -07001049
Doug Zongker37974732010-09-16 17:44:38 -07001050def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001051 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001052
Tao Bao9dd909e2017-11-14 11:27:32 -08001053 For non-AVB images, raise exception if the data is too big. Print a warning
1054 if the data is nearing the maximum size.
1055
1056 For AVB images, the actual image size should be identical to the limit.
1057
1058 Args:
1059 data: A string that contains all the data for the partition.
1060 target: The partition name. The ".img" suffix is optional.
1061 info_dict: The dict to be looked up for relevant info.
1062 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001063 if target.endswith(".img"):
1064 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001065 mount_point = "/" + target
1066
Ying Wangf8824af2014-06-03 14:07:27 -07001067 fs_type = None
1068 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001069 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001070 if mount_point == "/userdata":
1071 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001072 p = info_dict["fstab"][mount_point]
1073 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001074 device = p.device
1075 if "/" in device:
1076 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001077 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001078 if not fs_type or not limit:
1079 return
Doug Zongkereef39442009-04-02 12:14:19 -07001080
Andrew Boie0f9aec82012-02-14 09:32:52 -08001081 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001082 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1083 # path.
1084 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1085 if size != limit:
1086 raise ExternalError(
1087 "Mismatching image size for %s: expected %d actual %d" % (
1088 target, limit, size))
1089 else:
1090 pct = float(size) * 100.0 / limit
1091 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1092 if pct >= 99.0:
1093 raise ExternalError(msg)
1094 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001095 logger.warning("\n WARNING: %s\n", msg)
1096 else:
1097 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001098
1099
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001100def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001101 """Parses the APK certs info from a given target-files zip.
1102
1103 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1104 tuple with the following elements: (1) a dictionary that maps packages to
1105 certs (based on the "certificate" and "private_key" attributes in the file;
1106 (2) a string representing the extension of compressed APKs in the target files
1107 (e.g ".gz", ".bro").
1108
1109 Args:
1110 tf_zip: The input target_files ZipFile (already open).
1111
1112 Returns:
1113 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1114 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1115 no compressed APKs.
1116 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001117 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001118 compressed_extension = None
1119
Tao Bao0f990332017-09-08 19:02:54 -07001120 # META/apkcerts.txt contains the info for _all_ the packages known at build
1121 # time. Filter out the ones that are not installed.
1122 installed_files = set()
1123 for name in tf_zip.namelist():
1124 basename = os.path.basename(name)
1125 if basename:
1126 installed_files.add(basename)
1127
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001128 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1129 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001130 if not line:
1131 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001132 m = re.match(
1133 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1134 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1135 line)
1136 if not m:
1137 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001138
Tao Bao818ddf52018-01-05 11:17:34 -08001139 matches = m.groupdict()
1140 cert = matches["CERT"]
1141 privkey = matches["PRIVKEY"]
1142 name = matches["NAME"]
1143 this_compressed_extension = matches["COMPRESSED"]
1144
1145 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1146 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1147 if cert in SPECIAL_CERT_STRINGS and not privkey:
1148 certmap[name] = cert
1149 elif (cert.endswith(OPTIONS.public_key_suffix) and
1150 privkey.endswith(OPTIONS.private_key_suffix) and
1151 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1152 certmap[name] = cert[:-public_key_suffix_len]
1153 else:
1154 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1155
1156 if not this_compressed_extension:
1157 continue
1158
1159 # Only count the installed files.
1160 filename = name + '.' + this_compressed_extension
1161 if filename not in installed_files:
1162 continue
1163
1164 # Make sure that all the values in the compression map have the same
1165 # extension. We don't support multiple compression methods in the same
1166 # system image.
1167 if compressed_extension:
1168 if this_compressed_extension != compressed_extension:
1169 raise ValueError(
1170 "Multiple compressed extensions: {} vs {}".format(
1171 compressed_extension, this_compressed_extension))
1172 else:
1173 compressed_extension = this_compressed_extension
1174
1175 return (certmap,
1176 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001177
1178
Doug Zongkereef39442009-04-02 12:14:19 -07001179COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001180Global options
1181
1182 -p (--path) <dir>
1183 Prepend <dir>/bin to the list of places to search for binaries run by this
1184 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001185
Doug Zongker05d3dea2009-06-22 11:32:31 -07001186 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001187 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001188
Tao Bao30df8b42018-04-23 15:32:53 -07001189 -x (--extra) <key=value>
1190 Add a key/value pair to the 'extras' dict, which device-specific extension
1191 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001192
Doug Zongkereef39442009-04-02 12:14:19 -07001193 -v (--verbose)
1194 Show command lines being executed.
1195
1196 -h (--help)
1197 Display this usage message and exit.
1198"""
1199
1200def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001201 print(docstring.rstrip("\n"))
1202 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001203
1204
1205def ParseOptions(argv,
1206 docstring,
1207 extra_opts="", extra_long_opts=(),
1208 extra_option_handler=None):
1209 """Parse the options in argv and return any arguments that aren't
1210 flags. docstring is the calling module's docstring, to be displayed
1211 for errors and -h. extra_opts and extra_long_opts are for flags
1212 defined by the caller, which are processed by passing them to
1213 extra_option_handler."""
1214
1215 try:
1216 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001217 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001218 ["help", "verbose", "path=", "signapk_path=",
1219 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001220 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001221 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1222 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001223 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001224 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001225 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001226 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001227 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001228 sys.exit(2)
1229
Doug Zongkereef39442009-04-02 12:14:19 -07001230 for o, a in opts:
1231 if o in ("-h", "--help"):
1232 Usage(docstring)
1233 sys.exit()
1234 elif o in ("-v", "--verbose"):
1235 OPTIONS.verbose = True
1236 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001237 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001238 elif o in ("--signapk_path",):
1239 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001240 elif o in ("--signapk_shared_library_path",):
1241 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001242 elif o in ("--extra_signapk_args",):
1243 OPTIONS.extra_signapk_args = shlex.split(a)
1244 elif o in ("--java_path",):
1245 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001246 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001247 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001248 elif o in ("--public_key_suffix",):
1249 OPTIONS.public_key_suffix = a
1250 elif o in ("--private_key_suffix",):
1251 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001252 elif o in ("--boot_signer_path",):
1253 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001254 elif o in ("--boot_signer_args",):
1255 OPTIONS.boot_signer_args = shlex.split(a)
1256 elif o in ("--verity_signer_path",):
1257 OPTIONS.verity_signer_path = a
1258 elif o in ("--verity_signer_args",):
1259 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001260 elif o in ("-s", "--device_specific"):
1261 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001262 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001263 key, value = a.split("=", 1)
1264 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001265 else:
1266 if extra_option_handler is None or not extra_option_handler(o, a):
1267 assert False, "unknown option \"%s\"" % (o,)
1268
Doug Zongker85448772014-09-09 14:59:20 -07001269 if OPTIONS.search_path:
1270 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1271 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001272
1273 return args
1274
1275
Tao Bao4c851b12016-09-19 13:54:38 -07001276def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001277 """Make a temp file and add it to the list of things to be deleted
1278 when Cleanup() is called. Return the filename."""
1279 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1280 os.close(fd)
1281 OPTIONS.tempfiles.append(fn)
1282 return fn
1283
1284
Tao Bao1c830bf2017-12-25 10:43:47 -08001285def MakeTempDir(prefix='tmp', suffix=''):
1286 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1287
1288 Returns:
1289 The absolute pathname of the new directory.
1290 """
1291 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1292 OPTIONS.tempfiles.append(dir_name)
1293 return dir_name
1294
1295
Doug Zongkereef39442009-04-02 12:14:19 -07001296def Cleanup():
1297 for i in OPTIONS.tempfiles:
1298 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001299 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001300 else:
1301 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001302 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001303
1304
1305class PasswordManager(object):
1306 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001307 self.editor = os.getenv("EDITOR")
1308 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001309
1310 def GetPasswords(self, items):
1311 """Get passwords corresponding to each string in 'items',
1312 returning a dict. (The dict may have keys in addition to the
1313 values in 'items'.)
1314
1315 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1316 user edit that file to add more needed passwords. If no editor is
1317 available, or $ANDROID_PW_FILE isn't define, prompts the user
1318 interactively in the ordinary way.
1319 """
1320
1321 current = self.ReadFile()
1322
1323 first = True
1324 while True:
1325 missing = []
1326 for i in items:
1327 if i not in current or not current[i]:
1328 missing.append(i)
1329 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001330 if not missing:
1331 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001332
1333 for i in missing:
1334 current[i] = ""
1335
1336 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001337 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001338 answer = raw_input("try to edit again? [y]> ").strip()
1339 if answer and answer[0] not in 'yY':
1340 raise RuntimeError("key passwords unavailable")
1341 first = False
1342
1343 current = self.UpdateAndReadFile(current)
1344
Dan Albert8b72aef2015-03-23 19:13:21 -07001345 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001346 """Prompt the user to enter a value (password) for each key in
1347 'current' whose value is fales. Returns a new dict with all the
1348 values.
1349 """
1350 result = {}
1351 for k, v in sorted(current.iteritems()):
1352 if v:
1353 result[k] = v
1354 else:
1355 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001356 result[k] = getpass.getpass(
1357 "Enter password for %s key> " % k).strip()
1358 if result[k]:
1359 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001360 return result
1361
1362 def UpdateAndReadFile(self, current):
1363 if not self.editor or not self.pwfile:
1364 return self.PromptResult(current)
1365
1366 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001367 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001368 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1369 f.write("# (Additional spaces are harmless.)\n\n")
1370
1371 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001372 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1373 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001374 f.write("[[[ %s ]]] %s\n" % (v, k))
1375 if not v and first_line is None:
1376 # position cursor on first line with no password.
1377 first_line = i + 4
1378 f.close()
1379
Tao Bao986ee862018-10-04 15:46:16 -07001380 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001381
1382 return self.ReadFile()
1383
1384 def ReadFile(self):
1385 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001386 if self.pwfile is None:
1387 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001388 try:
1389 f = open(self.pwfile, "r")
1390 for line in f:
1391 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001392 if not line or line[0] == '#':
1393 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001394 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1395 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001396 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001397 else:
1398 result[m.group(2)] = m.group(1)
1399 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001400 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001401 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001402 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001403 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001404
1405
Dan Albert8e0178d2015-01-27 15:53:15 -08001406def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1407 compress_type=None):
1408 import datetime
1409
1410 # http://b/18015246
1411 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1412 # for files larger than 2GiB. We can work around this by adjusting their
1413 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1414 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1415 # it isn't clear to me exactly what circumstances cause this).
1416 # `zipfile.write()` must be used directly to work around this.
1417 #
1418 # This mess can be avoided if we port to python3.
1419 saved_zip64_limit = zipfile.ZIP64_LIMIT
1420 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1421
1422 if compress_type is None:
1423 compress_type = zip_file.compression
1424 if arcname is None:
1425 arcname = filename
1426
1427 saved_stat = os.stat(filename)
1428
1429 try:
1430 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1431 # file to be zipped and reset it when we're done.
1432 os.chmod(filename, perms)
1433
1434 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001435 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1436 # intentional. zip stores datetimes in local time without a time zone
1437 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1438 # in the zip archive.
1439 local_epoch = datetime.datetime.fromtimestamp(0)
1440 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001441 os.utime(filename, (timestamp, timestamp))
1442
1443 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1444 finally:
1445 os.chmod(filename, saved_stat.st_mode)
1446 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1447 zipfile.ZIP64_LIMIT = saved_zip64_limit
1448
1449
Tao Bao58c1b962015-05-20 09:32:18 -07001450def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001451 compress_type=None):
1452 """Wrap zipfile.writestr() function to work around the zip64 limit.
1453
1454 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1455 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1456 when calling crc32(bytes).
1457
1458 But it still works fine to write a shorter string into a large zip file.
1459 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1460 when we know the string won't be too long.
1461 """
1462
1463 saved_zip64_limit = zipfile.ZIP64_LIMIT
1464 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1465
1466 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1467 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001468 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001469 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001470 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001471 else:
Tao Baof3282b42015-04-01 11:21:55 -07001472 zinfo = zinfo_or_arcname
1473
1474 # If compress_type is given, it overrides the value in zinfo.
1475 if compress_type is not None:
1476 zinfo.compress_type = compress_type
1477
Tao Bao58c1b962015-05-20 09:32:18 -07001478 # If perms is given, it has a priority.
1479 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001480 # If perms doesn't set the file type, mark it as a regular file.
1481 if perms & 0o770000 == 0:
1482 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001483 zinfo.external_attr = perms << 16
1484
Tao Baof3282b42015-04-01 11:21:55 -07001485 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001486 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1487
Dan Albert8b72aef2015-03-23 19:13:21 -07001488 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001489 zipfile.ZIP64_LIMIT = saved_zip64_limit
1490
1491
Tao Bao89d7ab22017-12-14 17:05:33 -08001492def ZipDelete(zip_filename, entries):
1493 """Deletes entries from a ZIP file.
1494
1495 Since deleting entries from a ZIP file is not supported, it shells out to
1496 'zip -d'.
1497
1498 Args:
1499 zip_filename: The name of the ZIP file.
1500 entries: The name of the entry, or the list of names to be deleted.
1501
1502 Raises:
1503 AssertionError: In case of non-zero return from 'zip'.
1504 """
1505 if isinstance(entries, basestring):
1506 entries = [entries]
1507 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001508 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001509
1510
Tao Baof3282b42015-04-01 11:21:55 -07001511def ZipClose(zip_file):
1512 # http://b/18015246
1513 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1514 # central directory.
1515 saved_zip64_limit = zipfile.ZIP64_LIMIT
1516 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1517
1518 zip_file.close()
1519
1520 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001521
1522
1523class DeviceSpecificParams(object):
1524 module = None
1525 def __init__(self, **kwargs):
1526 """Keyword arguments to the constructor become attributes of this
1527 object, which is passed to all functions in the device-specific
1528 module."""
1529 for k, v in kwargs.iteritems():
1530 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001531 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001532
1533 if self.module is None:
1534 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001535 if not path:
1536 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001537 try:
1538 if os.path.isdir(path):
1539 info = imp.find_module("releasetools", [path])
1540 else:
1541 d, f = os.path.split(path)
1542 b, x = os.path.splitext(f)
1543 if x == ".py":
1544 f = b
1545 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001546 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001547 self.module = imp.load_module("device_specific", *info)
1548 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001549 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001550
1551 def _DoCall(self, function_name, *args, **kwargs):
1552 """Call the named function in the device-specific module, passing
1553 the given args and kwargs. The first argument to the call will be
1554 the DeviceSpecific object itself. If there is no module, or the
1555 module does not define the function, return the value of the
1556 'default' kwarg (which itself defaults to None)."""
1557 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001558 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001559 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1560
1561 def FullOTA_Assertions(self):
1562 """Called after emitting the block of assertions at the top of a
1563 full OTA package. Implementations can add whatever additional
1564 assertions they like."""
1565 return self._DoCall("FullOTA_Assertions")
1566
Doug Zongkere5ff5902012-01-17 10:55:37 -08001567 def FullOTA_InstallBegin(self):
1568 """Called at the start of full OTA installation."""
1569 return self._DoCall("FullOTA_InstallBegin")
1570
Yifan Hong10c530d2018-12-27 17:34:18 -08001571 def FullOTA_GetBlockDifferences(self):
1572 """Called during full OTA installation and verification.
1573 Implementation should return a list of BlockDifference objects describing
1574 the update on each additional partitions.
1575 """
1576 return self._DoCall("FullOTA_GetBlockDifferences")
1577
Doug Zongker05d3dea2009-06-22 11:32:31 -07001578 def FullOTA_InstallEnd(self):
1579 """Called at the end of full OTA installation; typically this is
1580 used to install the image for the device's baseband processor."""
1581 return self._DoCall("FullOTA_InstallEnd")
1582
1583 def IncrementalOTA_Assertions(self):
1584 """Called after emitting the block of assertions at the top of an
1585 incremental OTA package. Implementations can add whatever
1586 additional assertions they like."""
1587 return self._DoCall("IncrementalOTA_Assertions")
1588
Doug Zongkere5ff5902012-01-17 10:55:37 -08001589 def IncrementalOTA_VerifyBegin(self):
1590 """Called at the start of the verification phase of incremental
1591 OTA installation; additional checks can be placed here to abort
1592 the script before any changes are made."""
1593 return self._DoCall("IncrementalOTA_VerifyBegin")
1594
Doug Zongker05d3dea2009-06-22 11:32:31 -07001595 def IncrementalOTA_VerifyEnd(self):
1596 """Called at the end of the verification phase of incremental OTA
1597 installation; additional checks can be placed here to abort the
1598 script before any changes are made."""
1599 return self._DoCall("IncrementalOTA_VerifyEnd")
1600
Doug Zongkere5ff5902012-01-17 10:55:37 -08001601 def IncrementalOTA_InstallBegin(self):
1602 """Called at the start of incremental OTA installation (after
1603 verification is complete)."""
1604 return self._DoCall("IncrementalOTA_InstallBegin")
1605
Yifan Hong10c530d2018-12-27 17:34:18 -08001606 def IncrementalOTA_GetBlockDifferences(self):
1607 """Called during incremental OTA installation and verification.
1608 Implementation should return a list of BlockDifference objects describing
1609 the update on each additional partitions.
1610 """
1611 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1612
Doug Zongker05d3dea2009-06-22 11:32:31 -07001613 def IncrementalOTA_InstallEnd(self):
1614 """Called at the end of incremental OTA installation; typically
1615 this is used to install the image for the device's baseband
1616 processor."""
1617 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001618
Tao Bao9bc6bb22015-11-09 16:58:28 -08001619 def VerifyOTA_Assertions(self):
1620 return self._DoCall("VerifyOTA_Assertions")
1621
Tao Bao76def242017-11-21 09:25:31 -08001622
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001623class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001624 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001625 self.name = name
1626 self.data = data
1627 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001628 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001629 self.sha1 = sha1(data).hexdigest()
1630
1631 @classmethod
1632 def FromLocalFile(cls, name, diskname):
1633 f = open(diskname, "rb")
1634 data = f.read()
1635 f.close()
1636 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001637
1638 def WriteToTemp(self):
1639 t = tempfile.NamedTemporaryFile()
1640 t.write(self.data)
1641 t.flush()
1642 return t
1643
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001644 def WriteToDir(self, d):
1645 with open(os.path.join(d, self.name), "wb") as fp:
1646 fp.write(self.data)
1647
Geremy Condra36bd3652014-02-06 19:45:10 -08001648 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001649 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001650
Tao Bao76def242017-11-21 09:25:31 -08001651
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001652DIFF_PROGRAM_BY_EXT = {
1653 ".gz" : "imgdiff",
1654 ".zip" : ["imgdiff", "-z"],
1655 ".jar" : ["imgdiff", "-z"],
1656 ".apk" : ["imgdiff", "-z"],
1657 ".img" : "imgdiff",
1658 }
1659
Tao Bao76def242017-11-21 09:25:31 -08001660
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001661class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001662 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001663 self.tf = tf
1664 self.sf = sf
1665 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001666 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001667
1668 def ComputePatch(self):
1669 """Compute the patch (as a string of data) needed to turn sf into
1670 tf. Returns the same tuple as GetPatch()."""
1671
1672 tf = self.tf
1673 sf = self.sf
1674
Doug Zongker24cd2802012-08-14 16:36:15 -07001675 if self.diff_program:
1676 diff_program = self.diff_program
1677 else:
1678 ext = os.path.splitext(tf.name)[1]
1679 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001680
1681 ttemp = tf.WriteToTemp()
1682 stemp = sf.WriteToTemp()
1683
1684 ext = os.path.splitext(tf.name)[1]
1685
1686 try:
1687 ptemp = tempfile.NamedTemporaryFile()
1688 if isinstance(diff_program, list):
1689 cmd = copy.copy(diff_program)
1690 else:
1691 cmd = [diff_program]
1692 cmd.append(stemp.name)
1693 cmd.append(ttemp.name)
1694 cmd.append(ptemp.name)
1695 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001696 err = []
1697 def run():
1698 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001699 if e:
1700 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001701 th = threading.Thread(target=run)
1702 th.start()
1703 th.join(timeout=300) # 5 mins
1704 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001705 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001706 p.terminate()
1707 th.join(5)
1708 if th.is_alive():
1709 p.kill()
1710 th.join()
1711
Tianjie Xua2a9f992018-01-05 15:15:54 -08001712 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001713 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001714 self.patch = None
1715 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001716 diff = ptemp.read()
1717 finally:
1718 ptemp.close()
1719 stemp.close()
1720 ttemp.close()
1721
1722 self.patch = diff
1723 return self.tf, self.sf, self.patch
1724
1725
1726 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001727 """Returns a tuple of (target_file, source_file, patch_data).
1728
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001729 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001730 computing the patch failed.
1731 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001732 return self.tf, self.sf, self.patch
1733
1734
1735def ComputeDifferences(diffs):
1736 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001737 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001738
1739 # Do the largest files first, to try and reduce the long-pole effect.
1740 by_size = [(i.tf.size, i) for i in diffs]
1741 by_size.sort(reverse=True)
1742 by_size = [i[1] for i in by_size]
1743
1744 lock = threading.Lock()
1745 diff_iter = iter(by_size) # accessed under lock
1746
1747 def worker():
1748 try:
1749 lock.acquire()
1750 for d in diff_iter:
1751 lock.release()
1752 start = time.time()
1753 d.ComputePatch()
1754 dur = time.time() - start
1755 lock.acquire()
1756
1757 tf, sf, patch = d.GetPatch()
1758 if sf.name == tf.name:
1759 name = tf.name
1760 else:
1761 name = "%s (%s)" % (tf.name, sf.name)
1762 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001763 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001764 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001765 logger.info(
1766 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1767 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001768 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001769 except Exception:
1770 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001771 raise
1772
1773 # start worker threads; wait for them all to finish.
1774 threads = [threading.Thread(target=worker)
1775 for i in range(OPTIONS.worker_threads)]
1776 for th in threads:
1777 th.start()
1778 while threads:
1779 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001780
1781
Dan Albert8b72aef2015-03-23 19:13:21 -07001782class BlockDifference(object):
1783 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001784 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001785 self.tgt = tgt
1786 self.src = src
1787 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001788 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001789 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001790
Tao Baodd2a5892015-03-12 12:32:37 -07001791 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001792 version = max(
1793 int(i) for i in
1794 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001795 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001796 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001797
1798 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001799 version=self.version,
1800 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001801 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001802 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001803 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001804 self.touched_src_ranges = b.touched_src_ranges
1805 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001806
Yifan Hong10c530d2018-12-27 17:34:18 -08001807 # On devices with dynamic partitions, for new partitions,
1808 # src is None but OPTIONS.source_info_dict is not.
1809 if OPTIONS.source_info_dict is None:
1810 is_dynamic_build = OPTIONS.info_dict.get(
1811 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001812 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001813 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001814 is_dynamic_build = OPTIONS.source_info_dict.get(
1815 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001816 is_dynamic_source = partition in shlex.split(
1817 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001818
Yifan Hongbb2658d2019-01-25 12:30:58 -08001819 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001820 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1821
Yifan Hongbb2658d2019-01-25 12:30:58 -08001822 # For dynamic partitions builds, check partition list in both source
1823 # and target build because new partitions may be added, and existing
1824 # partitions may be removed.
1825 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1826
Yifan Hong10c530d2018-12-27 17:34:18 -08001827 if is_dynamic:
1828 self.device = 'map_partition("%s")' % partition
1829 else:
1830 if OPTIONS.source_info_dict is None:
1831 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1832 else:
1833 _, device_path = GetTypeAndDevice("/" + partition,
1834 OPTIONS.source_info_dict)
1835 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001836
Tao Baod8d14be2016-02-04 14:26:02 -08001837 @property
1838 def required_cache(self):
1839 return self._required_cache
1840
Tao Bao76def242017-11-21 09:25:31 -08001841 def WriteScript(self, script, output_zip, progress=None,
1842 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001843 if not self.src:
1844 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001845 script.Print("Patching %s image unconditionally..." % (self.partition,))
1846 else:
1847 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001848
Dan Albert8b72aef2015-03-23 19:13:21 -07001849 if progress:
1850 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001851 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001852
1853 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001854 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001855
Tao Bao9bc6bb22015-11-09 16:58:28 -08001856 def WriteStrictVerifyScript(self, script):
1857 """Verify all the blocks in the care_map, including clobbered blocks.
1858
1859 This differs from the WriteVerifyScript() function: a) it prints different
1860 error messages; b) it doesn't allow half-way updated images to pass the
1861 verification."""
1862
1863 partition = self.partition
1864 script.Print("Verifying %s..." % (partition,))
1865 ranges = self.tgt.care_map
1866 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001867 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001868 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1869 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001870 self.device, ranges_str,
1871 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001872 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001873 script.AppendExtra("")
1874
Tao Baod522bdc2016-04-12 15:53:16 -07001875 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001876 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001877
1878 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001879 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001880 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001881
1882 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001883 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001884 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001885 ranges = self.touched_src_ranges
1886 expected_sha1 = self.touched_src_sha1
1887 else:
1888 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1889 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001890
1891 # No blocks to be checked, skipping.
1892 if not ranges:
1893 return
1894
Tao Bao5ece99d2015-05-12 11:42:31 -07001895 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001896 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001897 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001898 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1899 '"%s.patch.dat")) then' % (
1900 self.device, ranges_str, expected_sha1,
1901 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001902 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001903 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001904
Tianjie Xufc3422a2015-12-15 11:53:59 -08001905 if self.version >= 4:
1906
1907 # Bug: 21124327
1908 # When generating incrementals for the system and vendor partitions in
1909 # version 4 or newer, explicitly check the first block (which contains
1910 # the superblock) of the partition to see if it's what we expect. If
1911 # this check fails, give an explicit log message about the partition
1912 # having been remounted R/W (the most likely explanation).
1913 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08001914 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08001915
1916 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001917 if partition == "system":
1918 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1919 else:
1920 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001921 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08001922 'ifelse (block_image_recover({device}, "{ranges}") && '
1923 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001924 'package_extract_file("{partition}.transfer.list"), '
1925 '"{partition}.new.dat", "{partition}.patch.dat"), '
1926 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001927 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001928 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001929 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001930
Tao Baodd2a5892015-03-12 12:32:37 -07001931 # Abort the OTA update. Note that the incremental OTA cannot be applied
1932 # even if it may match the checksum of the target partition.
1933 # a) If version < 3, operations like move and erase will make changes
1934 # unconditionally and damage the partition.
1935 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001936 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001937 if partition == "system":
1938 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1939 else:
1940 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1941 script.AppendExtra((
1942 'abort("E%d: %s partition has unexpected contents");\n'
1943 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001944
Yifan Hong10c530d2018-12-27 17:34:18 -08001945 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07001946 partition = self.partition
1947 script.Print('Verifying the updated %s image...' % (partition,))
1948 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1949 ranges = self.tgt.care_map
1950 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001951 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001952 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001953 self.device, ranges_str,
1954 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001955
1956 # Bug: 20881595
1957 # Verify that extended blocks are really zeroed out.
1958 if self.tgt.extended:
1959 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001960 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001961 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001962 self.device, ranges_str,
1963 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001964 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001965 if partition == "system":
1966 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1967 else:
1968 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001969 script.AppendExtra(
1970 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001971 ' abort("E%d: %s partition has unexpected non-zero contents after '
1972 'OTA update");\n'
1973 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001974 else:
1975 script.Print('Verified the updated %s image.' % (partition,))
1976
Tianjie Xu209db462016-05-24 17:34:52 -07001977 if partition == "system":
1978 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1979 else:
1980 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1981
Tao Bao5fcaaef2015-06-01 13:40:49 -07001982 script.AppendExtra(
1983 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001984 ' abort("E%d: %s partition has unexpected contents after OTA '
1985 'update");\n'
1986 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001987
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001988 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001989 ZipWrite(output_zip,
1990 '{}.transfer.list'.format(self.path),
1991 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001992
Tao Bao76def242017-11-21 09:25:31 -08001993 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1994 # its size. Quailty 9 almost triples the compression time but doesn't
1995 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001996 # zip | brotli(quality 6) | brotli(quality 9)
1997 # compressed_size: 942M | 869M (~8% reduced) | 854M
1998 # compression_time: 75s | 265s | 719s
1999 # decompression_time: 15s | 25s | 25s
2000
2001 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01002002 brotli_cmd = ['brotli', '--quality=6',
2003 '--output={}.new.dat.br'.format(self.path),
2004 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002005 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07002006 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002007
2008 new_data_name = '{}.new.dat.br'.format(self.partition)
2009 ZipWrite(output_zip,
2010 '{}.new.dat.br'.format(self.path),
2011 new_data_name,
2012 compress_type=zipfile.ZIP_STORED)
2013 else:
2014 new_data_name = '{}.new.dat'.format(self.partition)
2015 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2016
Dan Albert8e0178d2015-01-27 15:53:15 -08002017 ZipWrite(output_zip,
2018 '{}.patch.dat'.format(self.path),
2019 '{}.patch.dat'.format(self.partition),
2020 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002021
Tianjie Xu209db462016-05-24 17:34:52 -07002022 if self.partition == "system":
2023 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2024 else:
2025 code = ErrorCode.VENDOR_UPDATE_FAILURE
2026
Yifan Hong10c530d2018-12-27 17:34:18 -08002027 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002028 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002029 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002030 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002031 device=self.device, partition=self.partition,
2032 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002033 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002034
Dan Albert8b72aef2015-03-23 19:13:21 -07002035 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002036 data = source.ReadRangeSet(ranges)
2037 ctx = sha1()
2038
2039 for p in data:
2040 ctx.update(p)
2041
2042 return ctx.hexdigest()
2043
Tao Baoe9b61912015-07-09 17:37:49 -07002044 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2045 """Return the hash value for all zero blocks."""
2046 zero_block = '\x00' * 4096
2047 ctx = sha1()
2048 for _ in range(num_blocks):
2049 ctx.update(zero_block)
2050
2051 return ctx.hexdigest()
2052
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002053
2054DataImage = blockimgdiff.DataImage
2055
Tao Bao76def242017-11-21 09:25:31 -08002056
Doug Zongker96a57e72010-09-26 14:57:41 -07002057# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002058PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002059 "ext4": "EMMC",
2060 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002061 "f2fs": "EMMC",
2062 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002063}
Doug Zongker96a57e72010-09-26 14:57:41 -07002064
Tao Bao76def242017-11-21 09:25:31 -08002065
Doug Zongker96a57e72010-09-26 14:57:41 -07002066def GetTypeAndDevice(mount_point, info):
2067 fstab = info["fstab"]
2068 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002069 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2070 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002071 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002072 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002073
2074
2075def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002076 """Parses and converts a PEM-encoded certificate into DER-encoded.
2077
2078 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2079
2080 Returns:
2081 The decoded certificate string.
2082 """
2083 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002084 save = False
2085 for line in data.split("\n"):
2086 if "--END CERTIFICATE--" in line:
2087 break
2088 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002089 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002090 if "--BEGIN CERTIFICATE--" in line:
2091 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002092 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002093 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002094
Tao Bao04e1f012018-02-04 12:13:35 -08002095
2096def ExtractPublicKey(cert):
2097 """Extracts the public key (PEM-encoded) from the given certificate file.
2098
2099 Args:
2100 cert: The certificate filename.
2101
2102 Returns:
2103 The public key string.
2104
2105 Raises:
2106 AssertionError: On non-zero return from 'openssl'.
2107 """
2108 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2109 # While openssl 1.1 writes the key into the given filename followed by '-out',
2110 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2111 # stdout instead.
2112 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2113 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2114 pubkey, stderrdata = proc.communicate()
2115 assert proc.returncode == 0, \
2116 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2117 return pubkey
2118
2119
Doug Zongker412c02f2014-02-13 10:58:24 -08002120def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2121 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002122 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002123
Tao Bao6d5d6232018-03-09 17:04:42 -08002124 Most of the space in the boot and recovery images is just the kernel, which is
2125 identical for the two, so the resulting patch should be efficient. Add it to
2126 the output zip, along with a shell script that is run from init.rc on first
2127 boot to actually do the patching and install the new recovery image.
2128
2129 Args:
2130 input_dir: The top-level input directory of the target-files.zip.
2131 output_sink: The callback function that writes the result.
2132 recovery_img: File object for the recovery image.
2133 boot_img: File objects for the boot image.
2134 info_dict: A dict returned by common.LoadInfoDict() on the input
2135 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002136 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002137 if info_dict is None:
2138 info_dict = OPTIONS.info_dict
2139
Tao Bao6d5d6232018-03-09 17:04:42 -08002140 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002141
Tao Baof2cffbd2015-07-22 12:33:18 -07002142 if full_recovery_image:
2143 output_sink("etc/recovery.img", recovery_img.data)
2144
2145 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002146 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002147 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002148 # With system-root-image, boot and recovery images will have mismatching
2149 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2150 # to handle such a case.
2151 if system_root_image:
2152 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002153 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002154 assert not os.path.exists(path)
2155 else:
2156 diff_program = ["imgdiff"]
2157 if os.path.exists(path):
2158 diff_program.append("-b")
2159 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002160 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002161 else:
2162 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002163
2164 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2165 _, _, patch = d.ComputePatch()
2166 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002167
Dan Albertebb19aa2015-03-27 19:11:53 -07002168 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002169 # The following GetTypeAndDevice()s need to use the path in the target
2170 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002171 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2172 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2173 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002174 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002175
Tao Baof2cffbd2015-07-22 12:33:18 -07002176 if full_recovery_image:
2177 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002178if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2179 applypatch \\
2180 --flash /system/etc/recovery.img \\
2181 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2182 log -t recovery "Installing new recovery image: succeeded" || \\
2183 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002184else
2185 log -t recovery "Recovery image already installed"
2186fi
2187""" % {'type': recovery_type,
2188 'device': recovery_device,
2189 'sha1': recovery_img.sha1,
2190 'size': recovery_img.size}
2191 else:
2192 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002193if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2194 applypatch %(bonus_args)s \\
2195 --patch /system/recovery-from-boot.p \\
2196 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2197 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2198 log -t recovery "Installing new recovery image: succeeded" || \\
2199 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002200else
2201 log -t recovery "Recovery image already installed"
2202fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002203""" % {'boot_size': boot_img.size,
2204 'boot_sha1': boot_img.sha1,
2205 'recovery_size': recovery_img.size,
2206 'recovery_sha1': recovery_img.sha1,
2207 'boot_type': boot_type,
2208 'boot_device': boot_device,
2209 'recovery_type': recovery_type,
2210 'recovery_device': recovery_device,
2211 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002212
2213 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002214 # in the L release.
2215 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002216
Tao Bao32fcdab2018-10-12 10:30:39 -07002217 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002218
2219 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002220
2221
2222class DynamicPartitionUpdate(object):
2223 def __init__(self, src_group=None, tgt_group=None, progress=None,
2224 block_difference=None):
2225 self.src_group = src_group
2226 self.tgt_group = tgt_group
2227 self.progress = progress
2228 self.block_difference = block_difference
2229
2230 @property
2231 def src_size(self):
2232 if not self.block_difference:
2233 return 0
2234 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2235
2236 @property
2237 def tgt_size(self):
2238 if not self.block_difference:
2239 return 0
2240 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2241
2242 @staticmethod
2243 def _GetSparseImageSize(img):
2244 if not img:
2245 return 0
2246 return img.blocksize * img.total_blocks
2247
2248
2249class DynamicGroupUpdate(object):
2250 def __init__(self, src_size=None, tgt_size=None):
2251 # None: group does not exist. 0: no size limits.
2252 self.src_size = src_size
2253 self.tgt_size = tgt_size
2254
2255
2256class DynamicPartitionsDifference(object):
2257 def __init__(self, info_dict, block_diffs, progress_dict=None,
2258 source_info_dict=None):
2259 if progress_dict is None:
2260 progress_dict = dict()
2261
2262 self._remove_all_before_apply = False
2263 if source_info_dict is None:
2264 self._remove_all_before_apply = True
2265 source_info_dict = dict()
2266
2267 block_diff_dict = {e.partition:e for e in block_diffs}
2268 assert len(block_diff_dict) == len(block_diffs), \
2269 "Duplicated BlockDifference object for {}".format(
2270 [partition for partition, count in
2271 collections.Counter(e.partition for e in block_diffs).items()
2272 if count > 1])
2273
Yifan Hong79997e52019-01-23 16:56:19 -08002274 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002275
2276 for p, block_diff in block_diff_dict.items():
2277 self._partition_updates[p] = DynamicPartitionUpdate()
2278 self._partition_updates[p].block_difference = block_diff
2279
2280 for p, progress in progress_dict.items():
2281 if p in self._partition_updates:
2282 self._partition_updates[p].progress = progress
2283
2284 tgt_groups = shlex.split(info_dict.get(
2285 "super_partition_groups", "").strip())
2286 src_groups = shlex.split(source_info_dict.get(
2287 "super_partition_groups", "").strip())
2288
2289 for g in tgt_groups:
2290 for p in shlex.split(info_dict.get(
2291 "super_%s_partition_list" % g, "").strip()):
2292 assert p in self._partition_updates, \
2293 "{} is in target super_{}_partition_list but no BlockDifference " \
2294 "object is provided.".format(p, g)
2295 self._partition_updates[p].tgt_group = g
2296
2297 for g in src_groups:
2298 for p in shlex.split(source_info_dict.get(
2299 "super_%s_partition_list" % g, "").strip()):
2300 assert p in self._partition_updates, \
2301 "{} is in source super_{}_partition_list but no BlockDifference " \
2302 "object is provided.".format(p, g)
2303 self._partition_updates[p].src_group = g
2304
Yifan Hong45433e42019-01-18 13:55:25 -08002305 target_dynamic_partitions = set(shlex.split(info_dict.get(
2306 "dynamic_partition_list", "").strip()))
2307 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2308 if u.tgt_size)
2309 assert block_diffs_with_target == target_dynamic_partitions, \
2310 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2311 list(target_dynamic_partitions), list(block_diffs_with_target))
2312
2313 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2314 "dynamic_partition_list", "").strip()))
2315 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2316 if u.src_size)
2317 assert block_diffs_with_source == source_dynamic_partitions, \
2318 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2319 list(source_dynamic_partitions), list(block_diffs_with_source))
2320
Yifan Hong10c530d2018-12-27 17:34:18 -08002321 if self._partition_updates:
2322 logger.info("Updating dynamic partitions %s",
2323 self._partition_updates.keys())
2324
Yifan Hong79997e52019-01-23 16:56:19 -08002325 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002326
2327 for g in tgt_groups:
2328 self._group_updates[g] = DynamicGroupUpdate()
2329 self._group_updates[g].tgt_size = int(info_dict.get(
2330 "super_%s_group_size" % g, "0").strip())
2331
2332 for g in src_groups:
2333 if g not in self._group_updates:
2334 self._group_updates[g] = DynamicGroupUpdate()
2335 self._group_updates[g].src_size = int(source_info_dict.get(
2336 "super_%s_group_size" % g, "0").strip())
2337
2338 self._Compute()
2339
2340 def WriteScript(self, script, output_zip, write_verify_script=False):
2341 script.Comment('--- Start patching dynamic partitions ---')
2342 for p, u in self._partition_updates.items():
2343 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2344 script.Comment('Patch partition %s' % p)
2345 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2346 write_verify_script=False)
2347
2348 op_list_path = MakeTempFile()
2349 with open(op_list_path, 'w') as f:
2350 for line in self._op_list:
2351 f.write('{}\n'.format(line))
2352
2353 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2354
2355 script.Comment('Update dynamic partition metadata')
2356 script.AppendExtra('assert(update_dynamic_partitions('
2357 'package_extract_file("dynamic_partitions_op_list")));')
2358
2359 if write_verify_script:
2360 for p, u in self._partition_updates.items():
2361 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2362 u.block_difference.WritePostInstallVerifyScript(script)
2363 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2364
2365 for p, u in self._partition_updates.items():
2366 if u.tgt_size and u.src_size <= u.tgt_size:
2367 script.Comment('Patch partition %s' % p)
2368 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2369 write_verify_script=write_verify_script)
2370 if write_verify_script:
2371 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2372
2373 script.Comment('--- End patching dynamic partitions ---')
2374
2375 def _Compute(self):
2376 self._op_list = list()
2377
2378 def append(line):
2379 self._op_list.append(line)
2380
2381 def comment(line):
2382 self._op_list.append("# %s" % line)
2383
2384 if self._remove_all_before_apply:
2385 comment('Remove all existing dynamic partitions and groups before '
2386 'applying full OTA')
2387 append('remove_all_groups')
2388
2389 for p, u in self._partition_updates.items():
2390 if u.src_group and not u.tgt_group:
2391 append('remove %s' % p)
2392
2393 for p, u in self._partition_updates.items():
2394 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2395 comment('Move partition %s from %s to default' % (p, u.src_group))
2396 append('move %s default' % p)
2397
2398 for p, u in self._partition_updates.items():
2399 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2400 comment('Shrink partition %s from %d to %d' %
2401 (p, u.src_size, u.tgt_size))
2402 append('resize %s %s' % (p, u.tgt_size))
2403
2404 for g, u in self._group_updates.items():
2405 if u.src_size is not None and u.tgt_size is None:
2406 append('remove_group %s' % g)
2407 if (u.src_size is not None and u.tgt_size is not None and
2408 u.src_size > u.tgt_size):
2409 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2410 append('resize_group %s %d' % (g, u.tgt_size))
2411
2412 for g, u in self._group_updates.items():
2413 if u.src_size is None and u.tgt_size is not None:
2414 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2415 append('add_group %s %d' % (g, u.tgt_size))
2416 if (u.src_size is not None and u.tgt_size is not None and
2417 u.src_size < u.tgt_size):
2418 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2419 append('resize_group %s %d' % (g, u.tgt_size))
2420
2421 for p, u in self._partition_updates.items():
2422 if u.tgt_group and not u.src_group:
2423 comment('Add partition %s to group %s' % (p, u.tgt_group))
2424 append('add %s %s' % (p, u.tgt_group))
2425
2426 for p, u in self._partition_updates.items():
2427 if u.tgt_size and u.src_size < u.tgt_size:
2428 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2429 append('resize %s %d' % (p, u.tgt_size))
2430
2431 for p, u in self._partition_updates.items():
2432 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2433 comment('Move partition %s from default to %s' %
2434 (p, u.tgt_group))
2435 append('move %s %s' % (p, u.tgt_group))