blob: c685dd6ece51e651253a121a38f882eb251f96f7 [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
Doug Zongker75f17362009-12-08 13:46:44 -0800771def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800772 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800773
Tao Bao1c830bf2017-12-25 10:43:47 -0800774 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
775 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800776
Tao Bao1c830bf2017-12-25 10:43:47 -0800777 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800778 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800779 """
Doug Zongkereef39442009-04-02 12:14:19 -0700780
Doug Zongker55d93282011-01-25 17:03:34 -0800781 def unzip_to_dir(filename, dirname):
782 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
783 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800784 cmd.extend(pattern)
Tao Bao986ee862018-10-04 15:46:16 -0700785 RunAndCheckOutput(cmd)
Doug Zongker55d93282011-01-25 17:03:34 -0800786
Tao Bao1c830bf2017-12-25 10:43:47 -0800787 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800788 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
789 if m:
790 unzip_to_dir(m.group(1), tmp)
791 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
792 filename = m.group(1)
793 else:
794 unzip_to_dir(filename, tmp)
795
Tao Baodba59ee2018-01-09 13:21:02 -0800796 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700797
798
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700799def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
800 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800801 """Returns a SparseImage object suitable for passing to BlockImageDiff.
802
803 This function loads the specified sparse image from the given path, and
804 performs additional processing for OTA purpose. For example, it always adds
805 block 0 to clobbered blocks list. It also detects files that cannot be
806 reconstructed from the block list, for whom we should avoid applying imgdiff.
807
808 Args:
809 which: The partition name, which must be "system" or "vendor".
810 tmpdir: The directory that contains the prebuilt image and block map file.
811 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800812 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700813 hashtree_info_generator: If present, generates the hashtree_info for this
814 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800815 Returns:
816 A SparseImage object, with file_map info loaded.
817 """
818 assert which in ("system", "vendor")
819
820 path = os.path.join(tmpdir, "IMAGES", which + ".img")
821 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
822
823 # The image and map files must have been created prior to calling
824 # ota_from_target_files.py (since LMP).
825 assert os.path.exists(path) and os.path.exists(mappath)
826
827 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
828 # it to clobbered_blocks so that it will be written to the target
829 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
830 clobbered_blocks = "0"
831
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700832 image = sparse_img.SparseImage(
833 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
834 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800835
836 # block.map may contain less blocks, because mke2fs may skip allocating blocks
837 # if they contain all zeros. We can't reconstruct such a file from its block
838 # list. Tag such entries accordingly. (Bug: 65213616)
839 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800840 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700841 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800842 continue
843
Tom Cherryd14b8952018-08-09 14:26:00 -0700844 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
845 # filename listed in system.map may contain an additional leading slash
846 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
847 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700848 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
849
Tom Cherryd14b8952018-08-09 14:26:00 -0700850 # Special handling another case, where files not under /system
851 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700852 if which == 'system' and not arcname.startswith('SYSTEM'):
853 arcname = 'ROOT/' + arcname
854
855 assert arcname in input_zip.namelist(), \
856 "Failed to find the ZIP entry for {}".format(entry)
857
Tao Baoc765cca2018-01-31 17:32:40 -0800858 info = input_zip.getinfo(arcname)
859 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800860
861 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800862 # image, check the original block list to determine its completeness. Note
863 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800864 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800865 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800866
Tao Baoc765cca2018-01-31 17:32:40 -0800867 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
868 ranges.extra['incomplete'] = True
869
870 return image
871
872
Doug Zongkereef39442009-04-02 12:14:19 -0700873def GetKeyPasswords(keylist):
874 """Given a list of keys, prompt the user to enter passwords for
875 those which require them. Return a {key: password} dict. password
876 will be None if the key has no password."""
877
Doug Zongker8ce7c252009-05-22 13:34:54 -0700878 no_passwords = []
879 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700880 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700881 devnull = open("/dev/null", "w+b")
882 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800883 # We don't need a password for things that aren't really keys.
884 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700885 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700886 continue
887
T.R. Fullhart37e10522013-03-18 10:31:26 -0700888 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700889 "-inform", "DER", "-nocrypt"],
890 stdin=devnull.fileno(),
891 stdout=devnull.fileno(),
892 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700893 p.communicate()
894 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700895 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700896 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700897 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700898 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
899 "-inform", "DER", "-passin", "pass:"],
900 stdin=devnull.fileno(),
901 stdout=devnull.fileno(),
902 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700903 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700904 if p.returncode == 0:
905 # Encrypted key with empty string as password.
906 key_passwords[k] = ''
907 elif stderr.startswith('Error decrypting key'):
908 # Definitely encrypted key.
909 # It would have said "Error reading key" if it didn't parse correctly.
910 need_passwords.append(k)
911 else:
912 # Potentially, a type of key that openssl doesn't understand.
913 # We'll let the routines in signapk.jar handle it.
914 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700915 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700916
T.R. Fullhart37e10522013-03-18 10:31:26 -0700917 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800918 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700919 return key_passwords
920
921
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800922def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700923 """Gets the minSdkVersion declared in the APK.
924
925 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
926 This can be both a decimal number (API Level) or a codename.
927
928 Args:
929 apk_name: The APK filename.
930
931 Returns:
932 The parsed SDK version string.
933
934 Raises:
935 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800936 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700937 proc = Run(
938 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
939 stderr=subprocess.PIPE)
940 stdoutdata, stderrdata = proc.communicate()
941 if proc.returncode != 0:
942 raise ExternalError(
943 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
944 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800945
Tao Baof47bf0f2018-03-21 23:28:51 -0700946 for line in stdoutdata.split("\n"):
947 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800948 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
949 if m:
950 return m.group(1)
951 raise ExternalError("No minSdkVersion returned by aapt")
952
953
954def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700955 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800956
Tao Baof47bf0f2018-03-21 23:28:51 -0700957 If minSdkVersion is set to a codename, it is translated to a number using the
958 provided map.
959
960 Args:
961 apk_name: The APK filename.
962
963 Returns:
964 The parsed SDK version number.
965
966 Raises:
967 ExternalError: On failing to get the min SDK version number.
968 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800969 version = GetMinSdkVersion(apk_name)
970 try:
971 return int(version)
972 except ValueError:
973 # Not a decimal number. Codename?
974 if version in codename_to_api_level_map:
975 return codename_to_api_level_map[version]
976 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700977 raise ExternalError(
978 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
979 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800980
981
982def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800983 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700984 """Sign the input_name zip/jar/apk, producing output_name. Use the
985 given key and password (the latter may be None if the key does not
986 have a password.
987
Doug Zongker951495f2009-08-14 12:44:19 -0700988 If whole_file is true, use the "-w" option to SignApk to embed a
989 signature that covers the whole file in the archive comment of the
990 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800991
992 min_api_level is the API Level (int) of the oldest platform this file may end
993 up on. If not specified for an APK, the API Level is obtained by interpreting
994 the minSdkVersion attribute of the APK's AndroidManifest.xml.
995
996 codename_to_api_level_map is needed to translate the codename which may be
997 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700998 """
Tao Bao76def242017-11-21 09:25:31 -0800999 if codename_to_api_level_map is None:
1000 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -07001001
Alex Klyubin9667b182015-12-10 13:38:50 -08001002 java_library_path = os.path.join(
1003 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
1004
Tao Baoe95540e2016-11-08 12:08:53 -08001005 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
1006 ["-Djava.library.path=" + java_library_path,
1007 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
1008 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -07001009 if whole_file:
1010 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -08001011
1012 min_sdk_version = min_api_level
1013 if min_sdk_version is None:
1014 if not whole_file:
1015 min_sdk_version = GetMinSdkVersionInt(
1016 input_name, codename_to_api_level_map)
1017 if min_sdk_version is not None:
1018 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
1019
T.R. Fullhart37e10522013-03-18 10:31:26 -07001020 cmd.extend([key + OPTIONS.public_key_suffix,
1021 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -08001022 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001023
Tao Bao73dd4f42018-10-04 16:25:33 -07001024 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001025 if password is not None:
1026 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001027 stdoutdata, _ = proc.communicate(password)
1028 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001029 raise ExternalError(
1030 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001031 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001032
Doug Zongkereef39442009-04-02 12:14:19 -07001033
Doug Zongker37974732010-09-16 17:44:38 -07001034def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001035 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001036
Tao Bao9dd909e2017-11-14 11:27:32 -08001037 For non-AVB images, raise exception if the data is too big. Print a warning
1038 if the data is nearing the maximum size.
1039
1040 For AVB images, the actual image size should be identical to the limit.
1041
1042 Args:
1043 data: A string that contains all the data for the partition.
1044 target: The partition name. The ".img" suffix is optional.
1045 info_dict: The dict to be looked up for relevant info.
1046 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001047 if target.endswith(".img"):
1048 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001049 mount_point = "/" + target
1050
Ying Wangf8824af2014-06-03 14:07:27 -07001051 fs_type = None
1052 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001053 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001054 if mount_point == "/userdata":
1055 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001056 p = info_dict["fstab"][mount_point]
1057 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001058 device = p.device
1059 if "/" in device:
1060 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001061 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001062 if not fs_type or not limit:
1063 return
Doug Zongkereef39442009-04-02 12:14:19 -07001064
Andrew Boie0f9aec82012-02-14 09:32:52 -08001065 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001066 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1067 # path.
1068 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1069 if size != limit:
1070 raise ExternalError(
1071 "Mismatching image size for %s: expected %d actual %d" % (
1072 target, limit, size))
1073 else:
1074 pct = float(size) * 100.0 / limit
1075 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1076 if pct >= 99.0:
1077 raise ExternalError(msg)
1078 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001079 logger.warning("\n WARNING: %s\n", msg)
1080 else:
1081 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001082
1083
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001084def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001085 """Parses the APK certs info from a given target-files zip.
1086
1087 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1088 tuple with the following elements: (1) a dictionary that maps packages to
1089 certs (based on the "certificate" and "private_key" attributes in the file;
1090 (2) a string representing the extension of compressed APKs in the target files
1091 (e.g ".gz", ".bro").
1092
1093 Args:
1094 tf_zip: The input target_files ZipFile (already open).
1095
1096 Returns:
1097 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1098 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1099 no compressed APKs.
1100 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001101 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001102 compressed_extension = None
1103
Tao Bao0f990332017-09-08 19:02:54 -07001104 # META/apkcerts.txt contains the info for _all_ the packages known at build
1105 # time. Filter out the ones that are not installed.
1106 installed_files = set()
1107 for name in tf_zip.namelist():
1108 basename = os.path.basename(name)
1109 if basename:
1110 installed_files.add(basename)
1111
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001112 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1113 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001114 if not line:
1115 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001116 m = re.match(
1117 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1118 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1119 line)
1120 if not m:
1121 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001122
Tao Bao818ddf52018-01-05 11:17:34 -08001123 matches = m.groupdict()
1124 cert = matches["CERT"]
1125 privkey = matches["PRIVKEY"]
1126 name = matches["NAME"]
1127 this_compressed_extension = matches["COMPRESSED"]
1128
1129 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1130 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1131 if cert in SPECIAL_CERT_STRINGS and not privkey:
1132 certmap[name] = cert
1133 elif (cert.endswith(OPTIONS.public_key_suffix) and
1134 privkey.endswith(OPTIONS.private_key_suffix) and
1135 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1136 certmap[name] = cert[:-public_key_suffix_len]
1137 else:
1138 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1139
1140 if not this_compressed_extension:
1141 continue
1142
1143 # Only count the installed files.
1144 filename = name + '.' + this_compressed_extension
1145 if filename not in installed_files:
1146 continue
1147
1148 # Make sure that all the values in the compression map have the same
1149 # extension. We don't support multiple compression methods in the same
1150 # system image.
1151 if compressed_extension:
1152 if this_compressed_extension != compressed_extension:
1153 raise ValueError(
1154 "Multiple compressed extensions: {} vs {}".format(
1155 compressed_extension, this_compressed_extension))
1156 else:
1157 compressed_extension = this_compressed_extension
1158
1159 return (certmap,
1160 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001161
1162
Doug Zongkereef39442009-04-02 12:14:19 -07001163COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001164Global options
1165
1166 -p (--path) <dir>
1167 Prepend <dir>/bin to the list of places to search for binaries run by this
1168 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001169
Doug Zongker05d3dea2009-06-22 11:32:31 -07001170 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001171 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001172
Tao Bao30df8b42018-04-23 15:32:53 -07001173 -x (--extra) <key=value>
1174 Add a key/value pair to the 'extras' dict, which device-specific extension
1175 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001176
Doug Zongkereef39442009-04-02 12:14:19 -07001177 -v (--verbose)
1178 Show command lines being executed.
1179
1180 -h (--help)
1181 Display this usage message and exit.
1182"""
1183
1184def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001185 print(docstring.rstrip("\n"))
1186 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001187
1188
1189def ParseOptions(argv,
1190 docstring,
1191 extra_opts="", extra_long_opts=(),
1192 extra_option_handler=None):
1193 """Parse the options in argv and return any arguments that aren't
1194 flags. docstring is the calling module's docstring, to be displayed
1195 for errors and -h. extra_opts and extra_long_opts are for flags
1196 defined by the caller, which are processed by passing them to
1197 extra_option_handler."""
1198
1199 try:
1200 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001201 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001202 ["help", "verbose", "path=", "signapk_path=",
1203 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001204 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001205 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1206 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001207 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001208 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001209 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001210 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001211 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001212 sys.exit(2)
1213
Doug Zongkereef39442009-04-02 12:14:19 -07001214 for o, a in opts:
1215 if o in ("-h", "--help"):
1216 Usage(docstring)
1217 sys.exit()
1218 elif o in ("-v", "--verbose"):
1219 OPTIONS.verbose = True
1220 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001221 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001222 elif o in ("--signapk_path",):
1223 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001224 elif o in ("--signapk_shared_library_path",):
1225 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001226 elif o in ("--extra_signapk_args",):
1227 OPTIONS.extra_signapk_args = shlex.split(a)
1228 elif o in ("--java_path",):
1229 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001230 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001231 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001232 elif o in ("--public_key_suffix",):
1233 OPTIONS.public_key_suffix = a
1234 elif o in ("--private_key_suffix",):
1235 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001236 elif o in ("--boot_signer_path",):
1237 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001238 elif o in ("--boot_signer_args",):
1239 OPTIONS.boot_signer_args = shlex.split(a)
1240 elif o in ("--verity_signer_path",):
1241 OPTIONS.verity_signer_path = a
1242 elif o in ("--verity_signer_args",):
1243 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001244 elif o in ("-s", "--device_specific"):
1245 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001246 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001247 key, value = a.split("=", 1)
1248 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001249 else:
1250 if extra_option_handler is None or not extra_option_handler(o, a):
1251 assert False, "unknown option \"%s\"" % (o,)
1252
Doug Zongker85448772014-09-09 14:59:20 -07001253 if OPTIONS.search_path:
1254 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1255 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001256
1257 return args
1258
1259
Tao Bao4c851b12016-09-19 13:54:38 -07001260def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001261 """Make a temp file and add it to the list of things to be deleted
1262 when Cleanup() is called. Return the filename."""
1263 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1264 os.close(fd)
1265 OPTIONS.tempfiles.append(fn)
1266 return fn
1267
1268
Tao Bao1c830bf2017-12-25 10:43:47 -08001269def MakeTempDir(prefix='tmp', suffix=''):
1270 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1271
1272 Returns:
1273 The absolute pathname of the new directory.
1274 """
1275 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1276 OPTIONS.tempfiles.append(dir_name)
1277 return dir_name
1278
1279
Doug Zongkereef39442009-04-02 12:14:19 -07001280def Cleanup():
1281 for i in OPTIONS.tempfiles:
1282 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001283 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001284 else:
1285 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001286 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001287
1288
1289class PasswordManager(object):
1290 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001291 self.editor = os.getenv("EDITOR")
1292 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001293
1294 def GetPasswords(self, items):
1295 """Get passwords corresponding to each string in 'items',
1296 returning a dict. (The dict may have keys in addition to the
1297 values in 'items'.)
1298
1299 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1300 user edit that file to add more needed passwords. If no editor is
1301 available, or $ANDROID_PW_FILE isn't define, prompts the user
1302 interactively in the ordinary way.
1303 """
1304
1305 current = self.ReadFile()
1306
1307 first = True
1308 while True:
1309 missing = []
1310 for i in items:
1311 if i not in current or not current[i]:
1312 missing.append(i)
1313 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001314 if not missing:
1315 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001316
1317 for i in missing:
1318 current[i] = ""
1319
1320 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001321 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001322 answer = raw_input("try to edit again? [y]> ").strip()
1323 if answer and answer[0] not in 'yY':
1324 raise RuntimeError("key passwords unavailable")
1325 first = False
1326
1327 current = self.UpdateAndReadFile(current)
1328
Dan Albert8b72aef2015-03-23 19:13:21 -07001329 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001330 """Prompt the user to enter a value (password) for each key in
1331 'current' whose value is fales. Returns a new dict with all the
1332 values.
1333 """
1334 result = {}
1335 for k, v in sorted(current.iteritems()):
1336 if v:
1337 result[k] = v
1338 else:
1339 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001340 result[k] = getpass.getpass(
1341 "Enter password for %s key> " % k).strip()
1342 if result[k]:
1343 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001344 return result
1345
1346 def UpdateAndReadFile(self, current):
1347 if not self.editor or not self.pwfile:
1348 return self.PromptResult(current)
1349
1350 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001351 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001352 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1353 f.write("# (Additional spaces are harmless.)\n\n")
1354
1355 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001356 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1357 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001358 f.write("[[[ %s ]]] %s\n" % (v, k))
1359 if not v and first_line is None:
1360 # position cursor on first line with no password.
1361 first_line = i + 4
1362 f.close()
1363
Tao Bao986ee862018-10-04 15:46:16 -07001364 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001365
1366 return self.ReadFile()
1367
1368 def ReadFile(self):
1369 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001370 if self.pwfile is None:
1371 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001372 try:
1373 f = open(self.pwfile, "r")
1374 for line in f:
1375 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001376 if not line or line[0] == '#':
1377 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001378 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1379 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001380 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001381 else:
1382 result[m.group(2)] = m.group(1)
1383 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001384 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001385 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001386 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001387 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001388
1389
Dan Albert8e0178d2015-01-27 15:53:15 -08001390def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1391 compress_type=None):
1392 import datetime
1393
1394 # http://b/18015246
1395 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1396 # for files larger than 2GiB. We can work around this by adjusting their
1397 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1398 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1399 # it isn't clear to me exactly what circumstances cause this).
1400 # `zipfile.write()` must be used directly to work around this.
1401 #
1402 # This mess can be avoided if we port to python3.
1403 saved_zip64_limit = zipfile.ZIP64_LIMIT
1404 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1405
1406 if compress_type is None:
1407 compress_type = zip_file.compression
1408 if arcname is None:
1409 arcname = filename
1410
1411 saved_stat = os.stat(filename)
1412
1413 try:
1414 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1415 # file to be zipped and reset it when we're done.
1416 os.chmod(filename, perms)
1417
1418 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001419 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1420 # intentional. zip stores datetimes in local time without a time zone
1421 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1422 # in the zip archive.
1423 local_epoch = datetime.datetime.fromtimestamp(0)
1424 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001425 os.utime(filename, (timestamp, timestamp))
1426
1427 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1428 finally:
1429 os.chmod(filename, saved_stat.st_mode)
1430 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1431 zipfile.ZIP64_LIMIT = saved_zip64_limit
1432
1433
Tao Bao58c1b962015-05-20 09:32:18 -07001434def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001435 compress_type=None):
1436 """Wrap zipfile.writestr() function to work around the zip64 limit.
1437
1438 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1439 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1440 when calling crc32(bytes).
1441
1442 But it still works fine to write a shorter string into a large zip file.
1443 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1444 when we know the string won't be too long.
1445 """
1446
1447 saved_zip64_limit = zipfile.ZIP64_LIMIT
1448 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1449
1450 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1451 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001452 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001453 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001454 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001455 else:
Tao Baof3282b42015-04-01 11:21:55 -07001456 zinfo = zinfo_or_arcname
1457
1458 # If compress_type is given, it overrides the value in zinfo.
1459 if compress_type is not None:
1460 zinfo.compress_type = compress_type
1461
Tao Bao58c1b962015-05-20 09:32:18 -07001462 # If perms is given, it has a priority.
1463 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001464 # If perms doesn't set the file type, mark it as a regular file.
1465 if perms & 0o770000 == 0:
1466 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001467 zinfo.external_attr = perms << 16
1468
Tao Baof3282b42015-04-01 11:21:55 -07001469 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001470 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1471
Dan Albert8b72aef2015-03-23 19:13:21 -07001472 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001473 zipfile.ZIP64_LIMIT = saved_zip64_limit
1474
1475
Tao Bao89d7ab22017-12-14 17:05:33 -08001476def ZipDelete(zip_filename, entries):
1477 """Deletes entries from a ZIP file.
1478
1479 Since deleting entries from a ZIP file is not supported, it shells out to
1480 'zip -d'.
1481
1482 Args:
1483 zip_filename: The name of the ZIP file.
1484 entries: The name of the entry, or the list of names to be deleted.
1485
1486 Raises:
1487 AssertionError: In case of non-zero return from 'zip'.
1488 """
1489 if isinstance(entries, basestring):
1490 entries = [entries]
1491 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001492 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001493
1494
Tao Baof3282b42015-04-01 11:21:55 -07001495def ZipClose(zip_file):
1496 # http://b/18015246
1497 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1498 # central directory.
1499 saved_zip64_limit = zipfile.ZIP64_LIMIT
1500 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1501
1502 zip_file.close()
1503
1504 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001505
1506
1507class DeviceSpecificParams(object):
1508 module = None
1509 def __init__(self, **kwargs):
1510 """Keyword arguments to the constructor become attributes of this
1511 object, which is passed to all functions in the device-specific
1512 module."""
1513 for k, v in kwargs.iteritems():
1514 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001515 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001516
1517 if self.module is None:
1518 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001519 if not path:
1520 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001521 try:
1522 if os.path.isdir(path):
1523 info = imp.find_module("releasetools", [path])
1524 else:
1525 d, f = os.path.split(path)
1526 b, x = os.path.splitext(f)
1527 if x == ".py":
1528 f = b
1529 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001530 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001531 self.module = imp.load_module("device_specific", *info)
1532 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001533 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001534
1535 def _DoCall(self, function_name, *args, **kwargs):
1536 """Call the named function in the device-specific module, passing
1537 the given args and kwargs. The first argument to the call will be
1538 the DeviceSpecific object itself. If there is no module, or the
1539 module does not define the function, return the value of the
1540 'default' kwarg (which itself defaults to None)."""
1541 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001542 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001543 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1544
1545 def FullOTA_Assertions(self):
1546 """Called after emitting the block of assertions at the top of a
1547 full OTA package. Implementations can add whatever additional
1548 assertions they like."""
1549 return self._DoCall("FullOTA_Assertions")
1550
Doug Zongkere5ff5902012-01-17 10:55:37 -08001551 def FullOTA_InstallBegin(self):
1552 """Called at the start of full OTA installation."""
1553 return self._DoCall("FullOTA_InstallBegin")
1554
Yifan Hong10c530d2018-12-27 17:34:18 -08001555 def FullOTA_GetBlockDifferences(self):
1556 """Called during full OTA installation and verification.
1557 Implementation should return a list of BlockDifference objects describing
1558 the update on each additional partitions.
1559 """
1560 return self._DoCall("FullOTA_GetBlockDifferences")
1561
Doug Zongker05d3dea2009-06-22 11:32:31 -07001562 def FullOTA_InstallEnd(self):
1563 """Called at the end of full OTA installation; typically this is
1564 used to install the image for the device's baseband processor."""
1565 return self._DoCall("FullOTA_InstallEnd")
1566
1567 def IncrementalOTA_Assertions(self):
1568 """Called after emitting the block of assertions at the top of an
1569 incremental OTA package. Implementations can add whatever
1570 additional assertions they like."""
1571 return self._DoCall("IncrementalOTA_Assertions")
1572
Doug Zongkere5ff5902012-01-17 10:55:37 -08001573 def IncrementalOTA_VerifyBegin(self):
1574 """Called at the start of the verification phase of incremental
1575 OTA installation; additional checks can be placed here to abort
1576 the script before any changes are made."""
1577 return self._DoCall("IncrementalOTA_VerifyBegin")
1578
Doug Zongker05d3dea2009-06-22 11:32:31 -07001579 def IncrementalOTA_VerifyEnd(self):
1580 """Called at the end of the verification phase of incremental OTA
1581 installation; additional checks can be placed here to abort the
1582 script before any changes are made."""
1583 return self._DoCall("IncrementalOTA_VerifyEnd")
1584
Doug Zongkere5ff5902012-01-17 10:55:37 -08001585 def IncrementalOTA_InstallBegin(self):
1586 """Called at the start of incremental OTA installation (after
1587 verification is complete)."""
1588 return self._DoCall("IncrementalOTA_InstallBegin")
1589
Yifan Hong10c530d2018-12-27 17:34:18 -08001590 def IncrementalOTA_GetBlockDifferences(self):
1591 """Called during incremental OTA installation and verification.
1592 Implementation should return a list of BlockDifference objects describing
1593 the update on each additional partitions.
1594 """
1595 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1596
Doug Zongker05d3dea2009-06-22 11:32:31 -07001597 def IncrementalOTA_InstallEnd(self):
1598 """Called at the end of incremental OTA installation; typically
1599 this is used to install the image for the device's baseband
1600 processor."""
1601 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001602
Tao Bao9bc6bb22015-11-09 16:58:28 -08001603 def VerifyOTA_Assertions(self):
1604 return self._DoCall("VerifyOTA_Assertions")
1605
Tao Bao76def242017-11-21 09:25:31 -08001606
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001607class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001608 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001609 self.name = name
1610 self.data = data
1611 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001612 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001613 self.sha1 = sha1(data).hexdigest()
1614
1615 @classmethod
1616 def FromLocalFile(cls, name, diskname):
1617 f = open(diskname, "rb")
1618 data = f.read()
1619 f.close()
1620 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001621
1622 def WriteToTemp(self):
1623 t = tempfile.NamedTemporaryFile()
1624 t.write(self.data)
1625 t.flush()
1626 return t
1627
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001628 def WriteToDir(self, d):
1629 with open(os.path.join(d, self.name), "wb") as fp:
1630 fp.write(self.data)
1631
Geremy Condra36bd3652014-02-06 19:45:10 -08001632 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001633 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001634
Tao Bao76def242017-11-21 09:25:31 -08001635
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001636DIFF_PROGRAM_BY_EXT = {
1637 ".gz" : "imgdiff",
1638 ".zip" : ["imgdiff", "-z"],
1639 ".jar" : ["imgdiff", "-z"],
1640 ".apk" : ["imgdiff", "-z"],
1641 ".img" : "imgdiff",
1642 }
1643
Tao Bao76def242017-11-21 09:25:31 -08001644
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001645class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001646 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001647 self.tf = tf
1648 self.sf = sf
1649 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001650 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001651
1652 def ComputePatch(self):
1653 """Compute the patch (as a string of data) needed to turn sf into
1654 tf. Returns the same tuple as GetPatch()."""
1655
1656 tf = self.tf
1657 sf = self.sf
1658
Doug Zongker24cd2802012-08-14 16:36:15 -07001659 if self.diff_program:
1660 diff_program = self.diff_program
1661 else:
1662 ext = os.path.splitext(tf.name)[1]
1663 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001664
1665 ttemp = tf.WriteToTemp()
1666 stemp = sf.WriteToTemp()
1667
1668 ext = os.path.splitext(tf.name)[1]
1669
1670 try:
1671 ptemp = tempfile.NamedTemporaryFile()
1672 if isinstance(diff_program, list):
1673 cmd = copy.copy(diff_program)
1674 else:
1675 cmd = [diff_program]
1676 cmd.append(stemp.name)
1677 cmd.append(ttemp.name)
1678 cmd.append(ptemp.name)
1679 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001680 err = []
1681 def run():
1682 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001683 if e:
1684 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001685 th = threading.Thread(target=run)
1686 th.start()
1687 th.join(timeout=300) # 5 mins
1688 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001689 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001690 p.terminate()
1691 th.join(5)
1692 if th.is_alive():
1693 p.kill()
1694 th.join()
1695
Tianjie Xua2a9f992018-01-05 15:15:54 -08001696 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001697 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001698 self.patch = None
1699 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001700 diff = ptemp.read()
1701 finally:
1702 ptemp.close()
1703 stemp.close()
1704 ttemp.close()
1705
1706 self.patch = diff
1707 return self.tf, self.sf, self.patch
1708
1709
1710 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001711 """Returns a tuple of (target_file, source_file, patch_data).
1712
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001713 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001714 computing the patch failed.
1715 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001716 return self.tf, self.sf, self.patch
1717
1718
1719def ComputeDifferences(diffs):
1720 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001721 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001722
1723 # Do the largest files first, to try and reduce the long-pole effect.
1724 by_size = [(i.tf.size, i) for i in diffs]
1725 by_size.sort(reverse=True)
1726 by_size = [i[1] for i in by_size]
1727
1728 lock = threading.Lock()
1729 diff_iter = iter(by_size) # accessed under lock
1730
1731 def worker():
1732 try:
1733 lock.acquire()
1734 for d in diff_iter:
1735 lock.release()
1736 start = time.time()
1737 d.ComputePatch()
1738 dur = time.time() - start
1739 lock.acquire()
1740
1741 tf, sf, patch = d.GetPatch()
1742 if sf.name == tf.name:
1743 name = tf.name
1744 else:
1745 name = "%s (%s)" % (tf.name, sf.name)
1746 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001747 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001748 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001749 logger.info(
1750 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1751 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001752 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001753 except Exception:
1754 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001755 raise
1756
1757 # start worker threads; wait for them all to finish.
1758 threads = [threading.Thread(target=worker)
1759 for i in range(OPTIONS.worker_threads)]
1760 for th in threads:
1761 th.start()
1762 while threads:
1763 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001764
1765
Dan Albert8b72aef2015-03-23 19:13:21 -07001766class BlockDifference(object):
1767 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001768 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001769 self.tgt = tgt
1770 self.src = src
1771 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001772 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001773 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001774
Tao Baodd2a5892015-03-12 12:32:37 -07001775 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001776 version = max(
1777 int(i) for i in
1778 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001779 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001780 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001781
1782 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001783 version=self.version,
1784 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001785 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001786 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001787 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001788 self.touched_src_ranges = b.touched_src_ranges
1789 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001790
Yifan Hong10c530d2018-12-27 17:34:18 -08001791 # On devices with dynamic partitions, for new partitions,
1792 # src is None but OPTIONS.source_info_dict is not.
1793 if OPTIONS.source_info_dict is None:
1794 is_dynamic_build = OPTIONS.info_dict.get(
1795 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001796 is_dynamic_source = False
Tao Baoaac4ad52015-10-16 15:26:34 -07001797 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001798 is_dynamic_build = OPTIONS.source_info_dict.get(
1799 "use_dynamic_partitions") == "true"
Yifan Hongbb2658d2019-01-25 12:30:58 -08001800 is_dynamic_source = partition in shlex.split(
1801 OPTIONS.source_info_dict.get("dynamic_partition_list", "").strip())
Yifan Hong10c530d2018-12-27 17:34:18 -08001802
Yifan Hongbb2658d2019-01-25 12:30:58 -08001803 is_dynamic_target = partition in shlex.split(
Yifan Hong10c530d2018-12-27 17:34:18 -08001804 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1805
Yifan Hongbb2658d2019-01-25 12:30:58 -08001806 # For dynamic partitions builds, check partition list in both source
1807 # and target build because new partitions may be added, and existing
1808 # partitions may be removed.
1809 is_dynamic = is_dynamic_build and (is_dynamic_source or is_dynamic_target)
1810
Yifan Hong10c530d2018-12-27 17:34:18 -08001811 if is_dynamic:
1812 self.device = 'map_partition("%s")' % partition
1813 else:
1814 if OPTIONS.source_info_dict is None:
1815 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1816 else:
1817 _, device_path = GetTypeAndDevice("/" + partition,
1818 OPTIONS.source_info_dict)
1819 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001820
Tao Baod8d14be2016-02-04 14:26:02 -08001821 @property
1822 def required_cache(self):
1823 return self._required_cache
1824
Tao Bao76def242017-11-21 09:25:31 -08001825 def WriteScript(self, script, output_zip, progress=None,
1826 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001827 if not self.src:
1828 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001829 script.Print("Patching %s image unconditionally..." % (self.partition,))
1830 else:
1831 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001832
Dan Albert8b72aef2015-03-23 19:13:21 -07001833 if progress:
1834 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001835 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001836
1837 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001838 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001839
Tao Bao9bc6bb22015-11-09 16:58:28 -08001840 def WriteStrictVerifyScript(self, script):
1841 """Verify all the blocks in the care_map, including clobbered blocks.
1842
1843 This differs from the WriteVerifyScript() function: a) it prints different
1844 error messages; b) it doesn't allow half-way updated images to pass the
1845 verification."""
1846
1847 partition = self.partition
1848 script.Print("Verifying %s..." % (partition,))
1849 ranges = self.tgt.care_map
1850 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001851 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001852 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1853 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001854 self.device, ranges_str,
1855 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001856 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001857 script.AppendExtra("")
1858
Tao Baod522bdc2016-04-12 15:53:16 -07001859 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001860 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001861
1862 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001863 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001864 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001865
1866 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001867 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001868 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001869 ranges = self.touched_src_ranges
1870 expected_sha1 = self.touched_src_sha1
1871 else:
1872 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1873 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001874
1875 # No blocks to be checked, skipping.
1876 if not ranges:
1877 return
1878
Tao Bao5ece99d2015-05-12 11:42:31 -07001879 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001880 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001881 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001882 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1883 '"%s.patch.dat")) then' % (
1884 self.device, ranges_str, expected_sha1,
1885 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001886 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001887 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001888
Tianjie Xufc3422a2015-12-15 11:53:59 -08001889 if self.version >= 4:
1890
1891 # Bug: 21124327
1892 # When generating incrementals for the system and vendor partitions in
1893 # version 4 or newer, explicitly check the first block (which contains
1894 # the superblock) of the partition to see if it's what we expect. If
1895 # this check fails, give an explicit log message about the partition
1896 # having been remounted R/W (the most likely explanation).
1897 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08001898 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08001899
1900 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001901 if partition == "system":
1902 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1903 else:
1904 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001905 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08001906 'ifelse (block_image_recover({device}, "{ranges}") && '
1907 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001908 'package_extract_file("{partition}.transfer.list"), '
1909 '"{partition}.new.dat", "{partition}.patch.dat"), '
1910 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001911 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001912 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001913 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001914
Tao Baodd2a5892015-03-12 12:32:37 -07001915 # Abort the OTA update. Note that the incremental OTA cannot be applied
1916 # even if it may match the checksum of the target partition.
1917 # a) If version < 3, operations like move and erase will make changes
1918 # unconditionally and damage the partition.
1919 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001920 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001921 if partition == "system":
1922 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1923 else:
1924 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1925 script.AppendExtra((
1926 'abort("E%d: %s partition has unexpected contents");\n'
1927 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001928
Yifan Hong10c530d2018-12-27 17:34:18 -08001929 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07001930 partition = self.partition
1931 script.Print('Verifying the updated %s image...' % (partition,))
1932 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1933 ranges = self.tgt.care_map
1934 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001935 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001936 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001937 self.device, ranges_str,
1938 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001939
1940 # Bug: 20881595
1941 # Verify that extended blocks are really zeroed out.
1942 if self.tgt.extended:
1943 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001944 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001945 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001946 self.device, ranges_str,
1947 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001948 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001949 if partition == "system":
1950 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1951 else:
1952 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001953 script.AppendExtra(
1954 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001955 ' abort("E%d: %s partition has unexpected non-zero contents after '
1956 'OTA update");\n'
1957 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001958 else:
1959 script.Print('Verified the updated %s image.' % (partition,))
1960
Tianjie Xu209db462016-05-24 17:34:52 -07001961 if partition == "system":
1962 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1963 else:
1964 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1965
Tao Bao5fcaaef2015-06-01 13:40:49 -07001966 script.AppendExtra(
1967 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001968 ' abort("E%d: %s partition has unexpected contents after OTA '
1969 'update");\n'
1970 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001971
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001972 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001973 ZipWrite(output_zip,
1974 '{}.transfer.list'.format(self.path),
1975 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001976
Tao Bao76def242017-11-21 09:25:31 -08001977 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1978 # its size. Quailty 9 almost triples the compression time but doesn't
1979 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001980 # zip | brotli(quality 6) | brotli(quality 9)
1981 # compressed_size: 942M | 869M (~8% reduced) | 854M
1982 # compression_time: 75s | 265s | 719s
1983 # decompression_time: 15s | 25s | 25s
1984
1985 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001986 brotli_cmd = ['brotli', '--quality=6',
1987 '--output={}.new.dat.br'.format(self.path),
1988 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001989 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07001990 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001991
1992 new_data_name = '{}.new.dat.br'.format(self.partition)
1993 ZipWrite(output_zip,
1994 '{}.new.dat.br'.format(self.path),
1995 new_data_name,
1996 compress_type=zipfile.ZIP_STORED)
1997 else:
1998 new_data_name = '{}.new.dat'.format(self.partition)
1999 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
2000
Dan Albert8e0178d2015-01-27 15:53:15 -08002001 ZipWrite(output_zip,
2002 '{}.patch.dat'.format(self.path),
2003 '{}.patch.dat'.format(self.partition),
2004 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002005
Tianjie Xu209db462016-05-24 17:34:52 -07002006 if self.partition == "system":
2007 code = ErrorCode.SYSTEM_UPDATE_FAILURE
2008 else:
2009 code = ErrorCode.VENDOR_UPDATE_FAILURE
2010
Yifan Hong10c530d2018-12-27 17:34:18 -08002011 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08002012 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002013 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07002014 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07002015 device=self.device, partition=self.partition,
2016 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07002017 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002018
Dan Albert8b72aef2015-03-23 19:13:21 -07002019 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00002020 data = source.ReadRangeSet(ranges)
2021 ctx = sha1()
2022
2023 for p in data:
2024 ctx.update(p)
2025
2026 return ctx.hexdigest()
2027
Tao Baoe9b61912015-07-09 17:37:49 -07002028 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2029 """Return the hash value for all zero blocks."""
2030 zero_block = '\x00' * 4096
2031 ctx = sha1()
2032 for _ in range(num_blocks):
2033 ctx.update(zero_block)
2034
2035 return ctx.hexdigest()
2036
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002037
2038DataImage = blockimgdiff.DataImage
2039
Tao Bao76def242017-11-21 09:25:31 -08002040
Doug Zongker96a57e72010-09-26 14:57:41 -07002041# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002042PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002043 "ext4": "EMMC",
2044 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002045 "f2fs": "EMMC",
2046 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002047}
Doug Zongker96a57e72010-09-26 14:57:41 -07002048
Tao Bao76def242017-11-21 09:25:31 -08002049
Doug Zongker96a57e72010-09-26 14:57:41 -07002050def GetTypeAndDevice(mount_point, info):
2051 fstab = info["fstab"]
2052 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002053 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2054 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002055 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002056 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002057
2058
2059def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002060 """Parses and converts a PEM-encoded certificate into DER-encoded.
2061
2062 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2063
2064 Returns:
2065 The decoded certificate string.
2066 """
2067 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002068 save = False
2069 for line in data.split("\n"):
2070 if "--END CERTIFICATE--" in line:
2071 break
2072 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002073 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002074 if "--BEGIN CERTIFICATE--" in line:
2075 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002076 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002077 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002078
Tao Bao04e1f012018-02-04 12:13:35 -08002079
2080def ExtractPublicKey(cert):
2081 """Extracts the public key (PEM-encoded) from the given certificate file.
2082
2083 Args:
2084 cert: The certificate filename.
2085
2086 Returns:
2087 The public key string.
2088
2089 Raises:
2090 AssertionError: On non-zero return from 'openssl'.
2091 """
2092 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2093 # While openssl 1.1 writes the key into the given filename followed by '-out',
2094 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2095 # stdout instead.
2096 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2097 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2098 pubkey, stderrdata = proc.communicate()
2099 assert proc.returncode == 0, \
2100 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2101 return pubkey
2102
2103
Doug Zongker412c02f2014-02-13 10:58:24 -08002104def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2105 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002106 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002107
Tao Bao6d5d6232018-03-09 17:04:42 -08002108 Most of the space in the boot and recovery images is just the kernel, which is
2109 identical for the two, so the resulting patch should be efficient. Add it to
2110 the output zip, along with a shell script that is run from init.rc on first
2111 boot to actually do the patching and install the new recovery image.
2112
2113 Args:
2114 input_dir: The top-level input directory of the target-files.zip.
2115 output_sink: The callback function that writes the result.
2116 recovery_img: File object for the recovery image.
2117 boot_img: File objects for the boot image.
2118 info_dict: A dict returned by common.LoadInfoDict() on the input
2119 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002120 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002121 if info_dict is None:
2122 info_dict = OPTIONS.info_dict
2123
Tao Bao6d5d6232018-03-09 17:04:42 -08002124 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002125
Tao Baof2cffbd2015-07-22 12:33:18 -07002126 if full_recovery_image:
2127 output_sink("etc/recovery.img", recovery_img.data)
2128
2129 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002130 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002131 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002132 # With system-root-image, boot and recovery images will have mismatching
2133 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2134 # to handle such a case.
2135 if system_root_image:
2136 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002137 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002138 assert not os.path.exists(path)
2139 else:
2140 diff_program = ["imgdiff"]
2141 if os.path.exists(path):
2142 diff_program.append("-b")
2143 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002144 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002145 else:
2146 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002147
2148 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2149 _, _, patch = d.ComputePatch()
2150 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002151
Dan Albertebb19aa2015-03-27 19:11:53 -07002152 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002153 # The following GetTypeAndDevice()s need to use the path in the target
2154 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002155 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2156 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2157 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002158 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002159
Tao Baof2cffbd2015-07-22 12:33:18 -07002160 if full_recovery_image:
2161 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002162if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2163 applypatch \\
2164 --flash /system/etc/recovery.img \\
2165 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2166 log -t recovery "Installing new recovery image: succeeded" || \\
2167 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002168else
2169 log -t recovery "Recovery image already installed"
2170fi
2171""" % {'type': recovery_type,
2172 'device': recovery_device,
2173 'sha1': recovery_img.sha1,
2174 'size': recovery_img.size}
2175 else:
2176 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002177if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2178 applypatch %(bonus_args)s \\
2179 --patch /system/recovery-from-boot.p \\
2180 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2181 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2182 log -t recovery "Installing new recovery image: succeeded" || \\
2183 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002184else
2185 log -t recovery "Recovery image already installed"
2186fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002187""" % {'boot_size': boot_img.size,
2188 'boot_sha1': boot_img.sha1,
2189 'recovery_size': recovery_img.size,
2190 'recovery_sha1': recovery_img.sha1,
2191 'boot_type': boot_type,
2192 'boot_device': boot_device,
2193 'recovery_type': recovery_type,
2194 'recovery_device': recovery_device,
2195 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002196
2197 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002198 # in the L release.
2199 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002200
Tao Bao32fcdab2018-10-12 10:30:39 -07002201 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002202
2203 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002204
2205
2206class DynamicPartitionUpdate(object):
2207 def __init__(self, src_group=None, tgt_group=None, progress=None,
2208 block_difference=None):
2209 self.src_group = src_group
2210 self.tgt_group = tgt_group
2211 self.progress = progress
2212 self.block_difference = block_difference
2213
2214 @property
2215 def src_size(self):
2216 if not self.block_difference:
2217 return 0
2218 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2219
2220 @property
2221 def tgt_size(self):
2222 if not self.block_difference:
2223 return 0
2224 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2225
2226 @staticmethod
2227 def _GetSparseImageSize(img):
2228 if not img:
2229 return 0
2230 return img.blocksize * img.total_blocks
2231
2232
2233class DynamicGroupUpdate(object):
2234 def __init__(self, src_size=None, tgt_size=None):
2235 # None: group does not exist. 0: no size limits.
2236 self.src_size = src_size
2237 self.tgt_size = tgt_size
2238
2239
2240class DynamicPartitionsDifference(object):
2241 def __init__(self, info_dict, block_diffs, progress_dict=None,
2242 source_info_dict=None):
2243 if progress_dict is None:
2244 progress_dict = dict()
2245
2246 self._remove_all_before_apply = False
2247 if source_info_dict is None:
2248 self._remove_all_before_apply = True
2249 source_info_dict = dict()
2250
2251 block_diff_dict = {e.partition:e for e in block_diffs}
2252 assert len(block_diff_dict) == len(block_diffs), \
2253 "Duplicated BlockDifference object for {}".format(
2254 [partition for partition, count in
2255 collections.Counter(e.partition for e in block_diffs).items()
2256 if count > 1])
2257
Yifan Hong79997e52019-01-23 16:56:19 -08002258 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002259
2260 for p, block_diff in block_diff_dict.items():
2261 self._partition_updates[p] = DynamicPartitionUpdate()
2262 self._partition_updates[p].block_difference = block_diff
2263
2264 for p, progress in progress_dict.items():
2265 if p in self._partition_updates:
2266 self._partition_updates[p].progress = progress
2267
2268 tgt_groups = shlex.split(info_dict.get(
2269 "super_partition_groups", "").strip())
2270 src_groups = shlex.split(source_info_dict.get(
2271 "super_partition_groups", "").strip())
2272
2273 for g in tgt_groups:
2274 for p in shlex.split(info_dict.get(
2275 "super_%s_partition_list" % g, "").strip()):
2276 assert p in self._partition_updates, \
2277 "{} is in target super_{}_partition_list but no BlockDifference " \
2278 "object is provided.".format(p, g)
2279 self._partition_updates[p].tgt_group = g
2280
2281 for g in src_groups:
2282 for p in shlex.split(source_info_dict.get(
2283 "super_%s_partition_list" % g, "").strip()):
2284 assert p in self._partition_updates, \
2285 "{} is in source super_{}_partition_list but no BlockDifference " \
2286 "object is provided.".format(p, g)
2287 self._partition_updates[p].src_group = g
2288
Yifan Hong45433e42019-01-18 13:55:25 -08002289 target_dynamic_partitions = set(shlex.split(info_dict.get(
2290 "dynamic_partition_list", "").strip()))
2291 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2292 if u.tgt_size)
2293 assert block_diffs_with_target == target_dynamic_partitions, \
2294 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2295 list(target_dynamic_partitions), list(block_diffs_with_target))
2296
2297 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2298 "dynamic_partition_list", "").strip()))
2299 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2300 if u.src_size)
2301 assert block_diffs_with_source == source_dynamic_partitions, \
2302 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2303 list(source_dynamic_partitions), list(block_diffs_with_source))
2304
Yifan Hong10c530d2018-12-27 17:34:18 -08002305 if self._partition_updates:
2306 logger.info("Updating dynamic partitions %s",
2307 self._partition_updates.keys())
2308
Yifan Hong79997e52019-01-23 16:56:19 -08002309 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002310
2311 for g in tgt_groups:
2312 self._group_updates[g] = DynamicGroupUpdate()
2313 self._group_updates[g].tgt_size = int(info_dict.get(
2314 "super_%s_group_size" % g, "0").strip())
2315
2316 for g in src_groups:
2317 if g not in self._group_updates:
2318 self._group_updates[g] = DynamicGroupUpdate()
2319 self._group_updates[g].src_size = int(source_info_dict.get(
2320 "super_%s_group_size" % g, "0").strip())
2321
2322 self._Compute()
2323
2324 def WriteScript(self, script, output_zip, write_verify_script=False):
2325 script.Comment('--- Start patching dynamic partitions ---')
2326 for p, u in self._partition_updates.items():
2327 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2328 script.Comment('Patch partition %s' % p)
2329 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2330 write_verify_script=False)
2331
2332 op_list_path = MakeTempFile()
2333 with open(op_list_path, 'w') as f:
2334 for line in self._op_list:
2335 f.write('{}\n'.format(line))
2336
2337 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2338
2339 script.Comment('Update dynamic partition metadata')
2340 script.AppendExtra('assert(update_dynamic_partitions('
2341 'package_extract_file("dynamic_partitions_op_list")));')
2342
2343 if write_verify_script:
2344 for p, u in self._partition_updates.items():
2345 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2346 u.block_difference.WritePostInstallVerifyScript(script)
2347 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2348
2349 for p, u in self._partition_updates.items():
2350 if u.tgt_size and u.src_size <= u.tgt_size:
2351 script.Comment('Patch partition %s' % p)
2352 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2353 write_verify_script=write_verify_script)
2354 if write_verify_script:
2355 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2356
2357 script.Comment('--- End patching dynamic partitions ---')
2358
2359 def _Compute(self):
2360 self._op_list = list()
2361
2362 def append(line):
2363 self._op_list.append(line)
2364
2365 def comment(line):
2366 self._op_list.append("# %s" % line)
2367
2368 if self._remove_all_before_apply:
2369 comment('Remove all existing dynamic partitions and groups before '
2370 'applying full OTA')
2371 append('remove_all_groups')
2372
2373 for p, u in self._partition_updates.items():
2374 if u.src_group and not u.tgt_group:
2375 append('remove %s' % p)
2376
2377 for p, u in self._partition_updates.items():
2378 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2379 comment('Move partition %s from %s to default' % (p, u.src_group))
2380 append('move %s default' % p)
2381
2382 for p, u in self._partition_updates.items():
2383 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2384 comment('Shrink partition %s from %d to %d' %
2385 (p, u.src_size, u.tgt_size))
2386 append('resize %s %s' % (p, u.tgt_size))
2387
2388 for g, u in self._group_updates.items():
2389 if u.src_size is not None and u.tgt_size is None:
2390 append('remove_group %s' % g)
2391 if (u.src_size is not None and u.tgt_size is not None and
2392 u.src_size > u.tgt_size):
2393 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2394 append('resize_group %s %d' % (g, u.tgt_size))
2395
2396 for g, u in self._group_updates.items():
2397 if u.src_size is None and u.tgt_size is not None:
2398 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2399 append('add_group %s %d' % (g, u.tgt_size))
2400 if (u.src_size is not None and u.tgt_size is not None and
2401 u.src_size < u.tgt_size):
2402 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2403 append('resize_group %s %d' % (g, u.tgt_size))
2404
2405 for p, u in self._partition_updates.items():
2406 if u.tgt_group and not u.src_group:
2407 comment('Add partition %s to group %s' % (p, u.tgt_group))
2408 append('add %s %s' % (p, u.tgt_group))
2409
2410 for p, u in self._partition_updates.items():
2411 if u.tgt_size and u.src_size < u.tgt_size:
2412 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2413 append('resize %s %d' % (p, u.tgt_size))
2414
2415 for p, u in self._partition_updates.items():
2416 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2417 comment('Move partition %s from default to %s' %
2418 (p, u.tgt_group))
2419 append('move %s %s' % (p, u.tgt_group))