blob: be2c1084b4128783f66d3a6631140164909fb56a [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
Tao Bao986ee862018-10-04 15:46:16 -0700193def RunAndCheckOutput(args, verbose=None, **kwargs):
194 """Runs the given command and returns the output.
195
196 Args:
197 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700198 verbose: Whether the commands should be shown. Default to the global
199 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700200 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
204 Returns:
205 The output string.
206
207 Raises:
208 ExternalError: On non-zero exit from the command.
209 """
Tao Bao986ee862018-10-04 15:46:16 -0700210 proc = Run(args, verbose=verbose, **kwargs)
211 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700212 # Don't log any if caller explicitly says so.
213 if verbose != False:
214 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700215 if proc.returncode != 0:
216 raise ExternalError(
217 "Failed to run command '{}' (exit code {}):\n{}".format(
218 args, proc.returncode, output))
219 return output
220
221
Tao Baoc765cca2018-01-31 17:32:40 -0800222def RoundUpTo4K(value):
223 rounded_up = value + 4095
224 return rounded_up - (rounded_up % 4096)
225
226
Ying Wang7e6d4e42010-12-13 16:25:36 -0800227def CloseInheritedPipes():
228 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
229 before doing other work."""
230 if platform.system() != "Darwin":
231 return
232 for d in range(3, 1025):
233 try:
234 stat = os.fstat(d)
235 if stat is not None:
236 pipebit = stat[0] & 0x1000
237 if pipebit != 0:
238 os.close(d)
239 except OSError:
240 pass
241
242
Tao Bao410ad8b2018-08-24 12:08:38 -0700243def LoadInfoDict(input_file, repacking=False):
244 """Loads the key/value pairs from the given input target_files.
245
246 It reads `META/misc_info.txt` file in the target_files input, does sanity
247 checks and returns the parsed key/value pairs for to the given build. It's
248 usually called early when working on input target_files files, e.g. when
249 generating OTAs, or signing builds. Note that the function may be called
250 against an old target_files file (i.e. from past dessert releases). So the
251 property parsing needs to be backward compatible.
252
253 In a `META/misc_info.txt`, a few properties are stored as links to the files
254 in the PRODUCT_OUT directory. It works fine with the build system. However,
255 they are no longer available when (re)generating images from target_files zip.
256 When `repacking` is True, redirect these properties to the actual files in the
257 unzipped directory.
258
259 Args:
260 input_file: The input target_files file, which could be an open
261 zipfile.ZipFile instance, or a str for the dir that contains the files
262 unzipped from a target_files file.
263 repacking: Whether it's trying repack an target_files file after loading the
264 info dict (default: False). If so, it will rewrite a few loaded
265 properties (e.g. selinux_fc, root_dir) to point to the actual files in
266 target_files file. When doing repacking, `input_file` must be a dir.
267
268 Returns:
269 A dict that contains the parsed key/value pairs.
270
271 Raises:
272 AssertionError: On invalid input arguments.
273 ValueError: On malformed input values.
274 """
275 if repacking:
276 assert isinstance(input_file, str), \
277 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700278
Doug Zongkerc9253822014-02-04 12:17:58 -0800279 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700280 if isinstance(input_file, zipfile.ZipFile):
281 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800282 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700283 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800284 try:
285 with open(path) as f:
286 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700287 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800288 if e.errno == errno.ENOENT:
289 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800290
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700291 try:
Michael Runge6e836112014-04-15 17:40:21 -0700292 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700293 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700294 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700295
Tao Bao410ad8b2018-08-24 12:08:38 -0700296 if "recovery_api_version" not in d:
297 raise ValueError("Failed to find 'recovery_api_version'")
298 if "fstab_version" not in d:
299 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800300
Tao Bao410ad8b2018-08-24 12:08:38 -0700301 if repacking:
302 # We carry a copy of file_contexts.bin under META/. If not available, search
303 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
304 # images than the one running on device, in that case, we must have the one
305 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700306 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700307 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700308 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700309
Tom Cherryd14b8952018-08-09 14:26:00 -0700310 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700311
Tom Cherryd14b8952018-08-09 14:26:00 -0700312 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700313 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700314 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700315 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700316
Tao Baof54216f2016-03-29 15:12:37 -0700317 # Redirect {system,vendor}_base_fs_file.
318 if "system_base_fs_file" in d:
319 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700320 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700321 if os.path.exists(system_base_fs_file):
322 d["system_base_fs_file"] = system_base_fs_file
323 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700324 logger.warning(
325 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700326 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700327
328 if "vendor_base_fs_file" in d:
329 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700330 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700331 if os.path.exists(vendor_base_fs_file):
332 d["vendor_base_fs_file"] = vendor_base_fs_file
333 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700334 logger.warning(
335 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700336 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700337
Doug Zongker37974732010-09-16 17:44:38 -0700338 def makeint(key):
339 if key in d:
340 d[key] = int(d[key], 0)
341
342 makeint("recovery_api_version")
343 makeint("blocksize")
344 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700345 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700346 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700347 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700348 makeint("recovery_size")
349 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800350 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700351
Tao Baoa57ab9f2018-08-24 12:08:38 -0700352 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
353 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
354 # cases, since it may load the info_dict from an old build (e.g. when
355 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800356 system_root_image = d.get("system_root_image") == "true"
357 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700358 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700359 if isinstance(input_file, zipfile.ZipFile):
360 if recovery_fstab_path not in input_file.namelist():
361 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
362 else:
363 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
364 if not os.path.exists(path):
365 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800366 d["fstab"] = LoadRecoveryFSTab(
367 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700368
Tao Bao76def242017-11-21 09:25:31 -0800369 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700370 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700371 if isinstance(input_file, zipfile.ZipFile):
372 if recovery_fstab_path not in input_file.namelist():
373 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
374 else:
375 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
376 if not os.path.exists(path):
377 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800378 d["fstab"] = LoadRecoveryFSTab(
379 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700380
Tianjie Xucfa86222016-03-07 16:31:19 -0800381 else:
382 d["fstab"] = None
383
Tianjie Xu861f4132018-09-12 11:49:33 -0700384 # Tries to load the build props for all partitions with care_map, including
385 # system and vendor.
386 for partition in PARTITIONS_WITH_CARE_MAP:
387 d["{}.build.prop".format(partition)] = LoadBuildProp(
388 read_helper, "{}/build.prop".format(partition.upper()))
389 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800390
391 # Set up the salt (based on fingerprint or thumbprint) that will be used when
392 # adding AVB footer.
393 if d.get("avb_enable") == "true":
394 fp = None
395 if "build.prop" in d:
396 build_prop = d["build.prop"]
397 if "ro.build.fingerprint" in build_prop:
398 fp = build_prop["ro.build.fingerprint"]
399 elif "ro.build.thumbprint" in build_prop:
400 fp = build_prop["ro.build.thumbprint"]
401 if fp:
402 d["avb_salt"] = sha256(fp).hexdigest()
403
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700404 return d
405
Tao Baod1de6f32017-03-01 16:38:48 -0800406
Tao Baobcd1d162017-08-26 13:10:26 -0700407def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700408 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700409 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700410 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700411 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700412 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700413 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700414
Tao Baod1de6f32017-03-01 16:38:48 -0800415
Michael Runge6e836112014-04-15 17:40:21 -0700416def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700417 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700418 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700419 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700420 if not line or line.startswith("#"):
421 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700422 if "=" in line:
423 name, value = line.split("=", 1)
424 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700425 return d
426
Tao Baod1de6f32017-03-01 16:38:48 -0800427
Tianjie Xucfa86222016-03-07 16:31:19 -0800428def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
429 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700430 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800431 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700432 self.mount_point = mount_point
433 self.fs_type = fs_type
434 self.device = device
435 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700436 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700437
438 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800439 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700440 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700441 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700442 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700443
Tao Baod1de6f32017-03-01 16:38:48 -0800444 assert fstab_version == 2
445
446 d = {}
447 for line in data.split("\n"):
448 line = line.strip()
449 if not line or line.startswith("#"):
450 continue
451
452 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
453 pieces = line.split()
454 if len(pieces) != 5:
455 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
456
457 # Ignore entries that are managed by vold.
458 options = pieces[4]
459 if "voldmanaged=" in options:
460 continue
461
462 # It's a good line, parse it.
463 length = 0
464 options = options.split(",")
465 for i in options:
466 if i.startswith("length="):
467 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800468 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800469 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700470 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800471
Tao Baod1de6f32017-03-01 16:38:48 -0800472 mount_flags = pieces[3]
473 # Honor the SELinux context if present.
474 context = None
475 for i in mount_flags.split(","):
476 if i.startswith("context="):
477 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800478
Tao Baod1de6f32017-03-01 16:38:48 -0800479 mount_point = pieces[1]
480 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
481 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800482
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700483 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700484 # system. Other areas assume system is always at "/system" so point /system
485 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700486 if system_root_image:
487 assert not d.has_key("/system") and d.has_key("/")
488 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700489 return d
490
491
Doug Zongker37974732010-09-16 17:44:38 -0700492def DumpInfoDict(d):
493 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700494 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700495
Dan Albert8b72aef2015-03-23 19:13:21 -0700496
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800497def AppendAVBSigningArgs(cmd, partition):
498 """Append signing arguments for avbtool."""
499 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
500 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
501 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
502 if key_path and algorithm:
503 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700504 avb_salt = OPTIONS.info_dict.get("avb_salt")
505 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700506 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700507 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800508
509
Tao Bao02a08592018-07-22 12:40:45 -0700510def GetAvbChainedPartitionArg(partition, info_dict, key=None):
511 """Constructs and returns the arg to build or verify a chained partition.
512
513 Args:
514 partition: The partition name.
515 info_dict: The info dict to look up the key info and rollback index
516 location.
517 key: The key to be used for building or verifying the partition. Defaults to
518 the key listed in info_dict.
519
520 Returns:
521 A string of form "partition:rollback_index_location:key" that can be used to
522 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700523 """
524 if key is None:
525 key = info_dict["avb_" + partition + "_key_path"]
526 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
527 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
Tao Bao986ee862018-10-04 15:46:16 -0700528 RunAndCheckOutput(
Tao Bao73dd4f42018-10-04 16:25:33 -0700529 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path])
Tao Bao02a08592018-07-22 12:40:45 -0700530
531 rollback_index_location = info_dict[
532 "avb_" + partition + "_rollback_index_location"]
533 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
534
535
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700536def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800537 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700538 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700539
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700540 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800541 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
542 we are building a two-step special image (i.e. building a recovery image to
543 be loaded into /boot in two-step OTAs).
544
545 Return the image data, or None if sourcedir does not appear to contains files
546 for building the requested image.
547 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700548
549 def make_ramdisk():
550 ramdisk_img = tempfile.NamedTemporaryFile()
551
552 if os.access(fs_config_file, os.F_OK):
553 cmd = ["mkbootfs", "-f", fs_config_file,
554 os.path.join(sourcedir, "RAMDISK")]
555 else:
556 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
557 p1 = Run(cmd, stdout=subprocess.PIPE)
558 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
559
560 p2.wait()
561 p1.wait()
562 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
563 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
564
565 return ramdisk_img
566
567 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
568 return None
569
570 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700571 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700572
Doug Zongkerd5131602012-08-02 14:46:42 -0700573 if info_dict is None:
574 info_dict = OPTIONS.info_dict
575
Doug Zongkereef39442009-04-02 12:14:19 -0700576 img = tempfile.NamedTemporaryFile()
577
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700578 if has_ramdisk:
579 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700580
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800581 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
582 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
583
584 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700585
Benoit Fradina45a8682014-07-14 21:00:43 +0200586 fn = os.path.join(sourcedir, "second")
587 if os.access(fn, os.F_OK):
588 cmd.append("--second")
589 cmd.append(fn)
590
Doug Zongker171f1cd2009-06-15 22:36:37 -0700591 fn = os.path.join(sourcedir, "cmdline")
592 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700593 cmd.append("--cmdline")
594 cmd.append(open(fn).read().rstrip("\n"))
595
596 fn = os.path.join(sourcedir, "base")
597 if os.access(fn, os.F_OK):
598 cmd.append("--base")
599 cmd.append(open(fn).read().rstrip("\n"))
600
Ying Wang4de6b5b2010-08-25 14:29:34 -0700601 fn = os.path.join(sourcedir, "pagesize")
602 if os.access(fn, os.F_OK):
603 cmd.append("--pagesize")
604 cmd.append(open(fn).read().rstrip("\n"))
605
Tao Bao76def242017-11-21 09:25:31 -0800606 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700607 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700608 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700609
Tao Bao76def242017-11-21 09:25:31 -0800610 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000611 if args and args.strip():
612 cmd.extend(shlex.split(args))
613
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700614 if has_ramdisk:
615 cmd.extend(["--ramdisk", ramdisk_img.name])
616
Tao Baod95e9fd2015-03-29 23:07:41 -0700617 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800618 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700619 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700620 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700621 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700622 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700623
Tao Baobf70c312017-07-11 17:27:55 -0700624 # "boot" or "recovery", without extension.
625 partition_name = os.path.basename(sourcedir).lower()
626
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800627 if partition_name == "recovery":
628 if info_dict.get("include_recovery_dtbo") == "true":
629 fn = os.path.join(sourcedir, "recovery_dtbo")
630 cmd.extend(["--recovery_dtbo", fn])
631 if info_dict.get("include_recovery_acpio") == "true":
632 fn = os.path.join(sourcedir, "recovery_acpio")
633 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700634
Tao Bao986ee862018-10-04 15:46:16 -0700635 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700636
Tao Bao76def242017-11-21 09:25:31 -0800637 if (info_dict.get("boot_signer") == "true" and
638 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800639 # Hard-code the path as "/boot" for two-step special recovery image (which
640 # will be loaded into /boot during the two-step OTA).
641 if two_step_image:
642 path = "/boot"
643 else:
Tao Baobf70c312017-07-11 17:27:55 -0700644 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700645 cmd = [OPTIONS.boot_signer_path]
646 cmd.extend(OPTIONS.boot_signer_args)
647 cmd.extend([path, img.name,
648 info_dict["verity_key"] + ".pk8",
649 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700650 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700651
Tao Baod95e9fd2015-03-29 23:07:41 -0700652 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800653 elif info_dict.get("vboot"):
Tao Baobf70c312017-07-11 17:27:55 -0700654 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700655 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800656 # We have switched from the prebuilt futility binary to using the tool
657 # (futility-host) built from the source. Override the setting in the old
658 # TF.zip.
659 futility = info_dict["futility"]
660 if futility.startswith("prebuilts/"):
661 futility = "futility-host"
662 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700663 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700664 info_dict["vboot_key"] + ".vbprivk",
665 info_dict["vboot_subkey"] + ".vbprivk",
666 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700667 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700668 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700669
Tao Baof3282b42015-04-01 11:21:55 -0700670 # Clean up the temp files.
671 img_unsigned.close()
672 img_keyblock.close()
673
David Zeuthen8fecb282017-12-01 16:24:01 -0500674 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800675 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700676 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500677 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400678 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c312017-07-11 17:27:55 -0700679 "--partition_size", str(part_size), "--partition_name",
680 partition_name]
681 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500682 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400683 if args and args.strip():
684 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700685 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500686
687 img.seek(os.SEEK_SET, 0)
688 data = img.read()
689
690 if has_ramdisk:
691 ramdisk_img.close()
692 img.close()
693
694 return data
695
696
Doug Zongkerd5131602012-08-02 14:46:42 -0700697def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800698 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700699 """Return a File object with the desired bootable image.
700
701 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
702 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
703 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700704
Doug Zongker55d93282011-01-25 17:03:34 -0800705 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
706 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700707 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800708 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700709
710 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
711 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700712 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700713 return File.FromLocalFile(name, prebuilt_path)
714
Tao Bao32fcdab2018-10-12 10:30:39 -0700715 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700716
717 if info_dict is None:
718 info_dict = OPTIONS.info_dict
719
720 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800721 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
722 # for recovery.
723 has_ramdisk = (info_dict.get("system_root_image") != "true" or
724 prebuilt_name != "boot.img" or
725 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700726
Doug Zongker6f1d0312014-08-22 08:07:12 -0700727 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400728 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
729 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800730 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700731 if data:
732 return File(name, data)
733 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800734
Doug Zongkereef39442009-04-02 12:14:19 -0700735
Narayan Kamatha07bf042017-08-14 14:49:21 +0100736def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800737 """Gunzips the given gzip compressed file to a given output file."""
738 with gzip.open(in_filename, "rb") as in_file, \
739 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100740 shutil.copyfileobj(in_file, out_file)
741
742
Doug Zongker75f17362009-12-08 13:46:44 -0800743def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800744 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800745
Tao Bao1c830bf2017-12-25 10:43:47 -0800746 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
747 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800748
Tao Bao1c830bf2017-12-25 10:43:47 -0800749 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800750 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800751 """
Doug Zongkereef39442009-04-02 12:14:19 -0700752
Doug Zongker55d93282011-01-25 17:03:34 -0800753 def unzip_to_dir(filename, dirname):
754 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
755 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800756 cmd.extend(pattern)
Tao Bao986ee862018-10-04 15:46:16 -0700757 RunAndCheckOutput(cmd)
Doug Zongker55d93282011-01-25 17:03:34 -0800758
Tao Bao1c830bf2017-12-25 10:43:47 -0800759 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800760 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
761 if m:
762 unzip_to_dir(m.group(1), tmp)
763 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
764 filename = m.group(1)
765 else:
766 unzip_to_dir(filename, tmp)
767
Tao Baodba59ee2018-01-09 13:21:02 -0800768 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700769
770
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700771def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
772 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800773 """Returns a SparseImage object suitable for passing to BlockImageDiff.
774
775 This function loads the specified sparse image from the given path, and
776 performs additional processing for OTA purpose. For example, it always adds
777 block 0 to clobbered blocks list. It also detects files that cannot be
778 reconstructed from the block list, for whom we should avoid applying imgdiff.
779
780 Args:
781 which: The partition name, which must be "system" or "vendor".
782 tmpdir: The directory that contains the prebuilt image and block map file.
783 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800784 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700785 hashtree_info_generator: If present, generates the hashtree_info for this
786 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800787 Returns:
788 A SparseImage object, with file_map info loaded.
789 """
790 assert which in ("system", "vendor")
791
792 path = os.path.join(tmpdir, "IMAGES", which + ".img")
793 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
794
795 # The image and map files must have been created prior to calling
796 # ota_from_target_files.py (since LMP).
797 assert os.path.exists(path) and os.path.exists(mappath)
798
799 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
800 # it to clobbered_blocks so that it will be written to the target
801 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
802 clobbered_blocks = "0"
803
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700804 image = sparse_img.SparseImage(
805 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
806 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800807
808 # block.map may contain less blocks, because mke2fs may skip allocating blocks
809 # if they contain all zeros. We can't reconstruct such a file from its block
810 # list. Tag such entries accordingly. (Bug: 65213616)
811 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800812 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700813 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800814 continue
815
Tom Cherryd14b8952018-08-09 14:26:00 -0700816 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
817 # filename listed in system.map may contain an additional leading slash
818 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
819 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700820 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
821
Tom Cherryd14b8952018-08-09 14:26:00 -0700822 # Special handling another case, where files not under /system
823 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700824 if which == 'system' and not arcname.startswith('SYSTEM'):
825 arcname = 'ROOT/' + arcname
826
827 assert arcname in input_zip.namelist(), \
828 "Failed to find the ZIP entry for {}".format(entry)
829
Tao Baoc765cca2018-01-31 17:32:40 -0800830 info = input_zip.getinfo(arcname)
831 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800832
833 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800834 # image, check the original block list to determine its completeness. Note
835 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800836 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800837 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800838
Tao Baoc765cca2018-01-31 17:32:40 -0800839 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
840 ranges.extra['incomplete'] = True
841
842 return image
843
844
Doug Zongkereef39442009-04-02 12:14:19 -0700845def GetKeyPasswords(keylist):
846 """Given a list of keys, prompt the user to enter passwords for
847 those which require them. Return a {key: password} dict. password
848 will be None if the key has no password."""
849
Doug Zongker8ce7c252009-05-22 13:34:54 -0700850 no_passwords = []
851 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700852 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700853 devnull = open("/dev/null", "w+b")
854 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800855 # We don't need a password for things that aren't really keys.
856 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700857 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700858 continue
859
T.R. Fullhart37e10522013-03-18 10:31:26 -0700860 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700861 "-inform", "DER", "-nocrypt"],
862 stdin=devnull.fileno(),
863 stdout=devnull.fileno(),
864 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700865 p.communicate()
866 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700867 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700868 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700869 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700870 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
871 "-inform", "DER", "-passin", "pass:"],
872 stdin=devnull.fileno(),
873 stdout=devnull.fileno(),
874 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700875 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700876 if p.returncode == 0:
877 # Encrypted key with empty string as password.
878 key_passwords[k] = ''
879 elif stderr.startswith('Error decrypting key'):
880 # Definitely encrypted key.
881 # It would have said "Error reading key" if it didn't parse correctly.
882 need_passwords.append(k)
883 else:
884 # Potentially, a type of key that openssl doesn't understand.
885 # We'll let the routines in signapk.jar handle it.
886 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700887 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700888
T.R. Fullhart37e10522013-03-18 10:31:26 -0700889 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800890 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700891 return key_passwords
892
893
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800894def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700895 """Gets the minSdkVersion declared in the APK.
896
897 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
898 This can be both a decimal number (API Level) or a codename.
899
900 Args:
901 apk_name: The APK filename.
902
903 Returns:
904 The parsed SDK version string.
905
906 Raises:
907 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800908 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700909 proc = Run(
910 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
911 stderr=subprocess.PIPE)
912 stdoutdata, stderrdata = proc.communicate()
913 if proc.returncode != 0:
914 raise ExternalError(
915 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
916 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800917
Tao Baof47bf0f2018-03-21 23:28:51 -0700918 for line in stdoutdata.split("\n"):
919 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800920 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
921 if m:
922 return m.group(1)
923 raise ExternalError("No minSdkVersion returned by aapt")
924
925
926def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700927 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800928
Tao Baof47bf0f2018-03-21 23:28:51 -0700929 If minSdkVersion is set to a codename, it is translated to a number using the
930 provided map.
931
932 Args:
933 apk_name: The APK filename.
934
935 Returns:
936 The parsed SDK version number.
937
938 Raises:
939 ExternalError: On failing to get the min SDK version number.
940 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800941 version = GetMinSdkVersion(apk_name)
942 try:
943 return int(version)
944 except ValueError:
945 # Not a decimal number. Codename?
946 if version in codename_to_api_level_map:
947 return codename_to_api_level_map[version]
948 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700949 raise ExternalError(
950 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
951 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800952
953
954def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800955 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700956 """Sign the input_name zip/jar/apk, producing output_name. Use the
957 given key and password (the latter may be None if the key does not
958 have a password.
959
Doug Zongker951495f2009-08-14 12:44:19 -0700960 If whole_file is true, use the "-w" option to SignApk to embed a
961 signature that covers the whole file in the archive comment of the
962 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800963
964 min_api_level is the API Level (int) of the oldest platform this file may end
965 up on. If not specified for an APK, the API Level is obtained by interpreting
966 the minSdkVersion attribute of the APK's AndroidManifest.xml.
967
968 codename_to_api_level_map is needed to translate the codename which may be
969 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700970 """
Tao Bao76def242017-11-21 09:25:31 -0800971 if codename_to_api_level_map is None:
972 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700973
Alex Klyubin9667b182015-12-10 13:38:50 -0800974 java_library_path = os.path.join(
975 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
976
Tao Baoe95540e2016-11-08 12:08:53 -0800977 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
978 ["-Djava.library.path=" + java_library_path,
979 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
980 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700981 if whole_file:
982 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800983
984 min_sdk_version = min_api_level
985 if min_sdk_version is None:
986 if not whole_file:
987 min_sdk_version = GetMinSdkVersionInt(
988 input_name, codename_to_api_level_map)
989 if min_sdk_version is not None:
990 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
991
T.R. Fullhart37e10522013-03-18 10:31:26 -0700992 cmd.extend([key + OPTIONS.public_key_suffix,
993 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800994 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700995
Tao Bao73dd4f42018-10-04 16:25:33 -0700996 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -0700997 if password is not None:
998 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -0700999 stdoutdata, _ = proc.communicate(password)
1000 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001001 raise ExternalError(
1002 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001003 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001004
Doug Zongkereef39442009-04-02 12:14:19 -07001005
Doug Zongker37974732010-09-16 17:44:38 -07001006def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001007 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001008
Tao Bao9dd909e2017-11-14 11:27:32 -08001009 For non-AVB images, raise exception if the data is too big. Print a warning
1010 if the data is nearing the maximum size.
1011
1012 For AVB images, the actual image size should be identical to the limit.
1013
1014 Args:
1015 data: A string that contains all the data for the partition.
1016 target: The partition name. The ".img" suffix is optional.
1017 info_dict: The dict to be looked up for relevant info.
1018 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001019 if target.endswith(".img"):
1020 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001021 mount_point = "/" + target
1022
Ying Wangf8824af2014-06-03 14:07:27 -07001023 fs_type = None
1024 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001025 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001026 if mount_point == "/userdata":
1027 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001028 p = info_dict["fstab"][mount_point]
1029 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001030 device = p.device
1031 if "/" in device:
1032 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001033 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001034 if not fs_type or not limit:
1035 return
Doug Zongkereef39442009-04-02 12:14:19 -07001036
Andrew Boie0f9aec82012-02-14 09:32:52 -08001037 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001038 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1039 # path.
1040 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1041 if size != limit:
1042 raise ExternalError(
1043 "Mismatching image size for %s: expected %d actual %d" % (
1044 target, limit, size))
1045 else:
1046 pct = float(size) * 100.0 / limit
1047 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1048 if pct >= 99.0:
1049 raise ExternalError(msg)
1050 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001051 logger.warning("\n WARNING: %s\n", msg)
1052 else:
1053 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001054
1055
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001056def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001057 """Parses the APK certs info from a given target-files zip.
1058
1059 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1060 tuple with the following elements: (1) a dictionary that maps packages to
1061 certs (based on the "certificate" and "private_key" attributes in the file;
1062 (2) a string representing the extension of compressed APKs in the target files
1063 (e.g ".gz", ".bro").
1064
1065 Args:
1066 tf_zip: The input target_files ZipFile (already open).
1067
1068 Returns:
1069 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1070 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1071 no compressed APKs.
1072 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001073 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001074 compressed_extension = None
1075
Tao Bao0f990332017-09-08 19:02:54 -07001076 # META/apkcerts.txt contains the info for _all_ the packages known at build
1077 # time. Filter out the ones that are not installed.
1078 installed_files = set()
1079 for name in tf_zip.namelist():
1080 basename = os.path.basename(name)
1081 if basename:
1082 installed_files.add(basename)
1083
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001084 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1085 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001086 if not line:
1087 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001088 m = re.match(
1089 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1090 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1091 line)
1092 if not m:
1093 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001094
Tao Bao818ddf52018-01-05 11:17:34 -08001095 matches = m.groupdict()
1096 cert = matches["CERT"]
1097 privkey = matches["PRIVKEY"]
1098 name = matches["NAME"]
1099 this_compressed_extension = matches["COMPRESSED"]
1100
1101 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1102 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1103 if cert in SPECIAL_CERT_STRINGS and not privkey:
1104 certmap[name] = cert
1105 elif (cert.endswith(OPTIONS.public_key_suffix) and
1106 privkey.endswith(OPTIONS.private_key_suffix) and
1107 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1108 certmap[name] = cert[:-public_key_suffix_len]
1109 else:
1110 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1111
1112 if not this_compressed_extension:
1113 continue
1114
1115 # Only count the installed files.
1116 filename = name + '.' + this_compressed_extension
1117 if filename not in installed_files:
1118 continue
1119
1120 # Make sure that all the values in the compression map have the same
1121 # extension. We don't support multiple compression methods in the same
1122 # system image.
1123 if compressed_extension:
1124 if this_compressed_extension != compressed_extension:
1125 raise ValueError(
1126 "Multiple compressed extensions: {} vs {}".format(
1127 compressed_extension, this_compressed_extension))
1128 else:
1129 compressed_extension = this_compressed_extension
1130
1131 return (certmap,
1132 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001133
1134
Doug Zongkereef39442009-04-02 12:14:19 -07001135COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001136Global options
1137
1138 -p (--path) <dir>
1139 Prepend <dir>/bin to the list of places to search for binaries run by this
1140 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001141
Doug Zongker05d3dea2009-06-22 11:32:31 -07001142 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001143 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001144
Tao Bao30df8b42018-04-23 15:32:53 -07001145 -x (--extra) <key=value>
1146 Add a key/value pair to the 'extras' dict, which device-specific extension
1147 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001148
Doug Zongkereef39442009-04-02 12:14:19 -07001149 -v (--verbose)
1150 Show command lines being executed.
1151
1152 -h (--help)
1153 Display this usage message and exit.
1154"""
1155
1156def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001157 print(docstring.rstrip("\n"))
1158 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001159
1160
1161def ParseOptions(argv,
1162 docstring,
1163 extra_opts="", extra_long_opts=(),
1164 extra_option_handler=None):
1165 """Parse the options in argv and return any arguments that aren't
1166 flags. docstring is the calling module's docstring, to be displayed
1167 for errors and -h. extra_opts and extra_long_opts are for flags
1168 defined by the caller, which are processed by passing them to
1169 extra_option_handler."""
1170
1171 try:
1172 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001173 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001174 ["help", "verbose", "path=", "signapk_path=",
1175 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001176 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001177 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1178 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001179 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001180 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001181 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001182 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001183 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001184 sys.exit(2)
1185
Doug Zongkereef39442009-04-02 12:14:19 -07001186 for o, a in opts:
1187 if o in ("-h", "--help"):
1188 Usage(docstring)
1189 sys.exit()
1190 elif o in ("-v", "--verbose"):
1191 OPTIONS.verbose = True
1192 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001193 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001194 elif o in ("--signapk_path",):
1195 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001196 elif o in ("--signapk_shared_library_path",):
1197 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001198 elif o in ("--extra_signapk_args",):
1199 OPTIONS.extra_signapk_args = shlex.split(a)
1200 elif o in ("--java_path",):
1201 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001202 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001203 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001204 elif o in ("--public_key_suffix",):
1205 OPTIONS.public_key_suffix = a
1206 elif o in ("--private_key_suffix",):
1207 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001208 elif o in ("--boot_signer_path",):
1209 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001210 elif o in ("--boot_signer_args",):
1211 OPTIONS.boot_signer_args = shlex.split(a)
1212 elif o in ("--verity_signer_path",):
1213 OPTIONS.verity_signer_path = a
1214 elif o in ("--verity_signer_args",):
1215 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001216 elif o in ("-s", "--device_specific"):
1217 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001218 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001219 key, value = a.split("=", 1)
1220 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001221 else:
1222 if extra_option_handler is None or not extra_option_handler(o, a):
1223 assert False, "unknown option \"%s\"" % (o,)
1224
Doug Zongker85448772014-09-09 14:59:20 -07001225 if OPTIONS.search_path:
1226 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1227 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001228
1229 return args
1230
1231
Tao Bao4c851b12016-09-19 13:54:38 -07001232def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001233 """Make a temp file and add it to the list of things to be deleted
1234 when Cleanup() is called. Return the filename."""
1235 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1236 os.close(fd)
1237 OPTIONS.tempfiles.append(fn)
1238 return fn
1239
1240
Tao Bao1c830bf2017-12-25 10:43:47 -08001241def MakeTempDir(prefix='tmp', suffix=''):
1242 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1243
1244 Returns:
1245 The absolute pathname of the new directory.
1246 """
1247 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1248 OPTIONS.tempfiles.append(dir_name)
1249 return dir_name
1250
1251
Doug Zongkereef39442009-04-02 12:14:19 -07001252def Cleanup():
1253 for i in OPTIONS.tempfiles:
1254 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001255 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001256 else:
1257 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001258 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001259
1260
1261class PasswordManager(object):
1262 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001263 self.editor = os.getenv("EDITOR")
1264 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001265
1266 def GetPasswords(self, items):
1267 """Get passwords corresponding to each string in 'items',
1268 returning a dict. (The dict may have keys in addition to the
1269 values in 'items'.)
1270
1271 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1272 user edit that file to add more needed passwords. If no editor is
1273 available, or $ANDROID_PW_FILE isn't define, prompts the user
1274 interactively in the ordinary way.
1275 """
1276
1277 current = self.ReadFile()
1278
1279 first = True
1280 while True:
1281 missing = []
1282 for i in items:
1283 if i not in current or not current[i]:
1284 missing.append(i)
1285 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001286 if not missing:
1287 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001288
1289 for i in missing:
1290 current[i] = ""
1291
1292 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001293 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001294 answer = raw_input("try to edit again? [y]> ").strip()
1295 if answer and answer[0] not in 'yY':
1296 raise RuntimeError("key passwords unavailable")
1297 first = False
1298
1299 current = self.UpdateAndReadFile(current)
1300
Dan Albert8b72aef2015-03-23 19:13:21 -07001301 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001302 """Prompt the user to enter a value (password) for each key in
1303 'current' whose value is fales. Returns a new dict with all the
1304 values.
1305 """
1306 result = {}
1307 for k, v in sorted(current.iteritems()):
1308 if v:
1309 result[k] = v
1310 else:
1311 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001312 result[k] = getpass.getpass(
1313 "Enter password for %s key> " % k).strip()
1314 if result[k]:
1315 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001316 return result
1317
1318 def UpdateAndReadFile(self, current):
1319 if not self.editor or not self.pwfile:
1320 return self.PromptResult(current)
1321
1322 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001323 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001324 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1325 f.write("# (Additional spaces are harmless.)\n\n")
1326
1327 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001328 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1329 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001330 f.write("[[[ %s ]]] %s\n" % (v, k))
1331 if not v and first_line is None:
1332 # position cursor on first line with no password.
1333 first_line = i + 4
1334 f.close()
1335
Tao Bao986ee862018-10-04 15:46:16 -07001336 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001337
1338 return self.ReadFile()
1339
1340 def ReadFile(self):
1341 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001342 if self.pwfile is None:
1343 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001344 try:
1345 f = open(self.pwfile, "r")
1346 for line in f:
1347 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001348 if not line or line[0] == '#':
1349 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001350 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1351 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001352 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001353 else:
1354 result[m.group(2)] = m.group(1)
1355 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001356 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001357 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001358 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001359 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001360
1361
Dan Albert8e0178d2015-01-27 15:53:15 -08001362def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1363 compress_type=None):
1364 import datetime
1365
1366 # http://b/18015246
1367 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1368 # for files larger than 2GiB. We can work around this by adjusting their
1369 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1370 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1371 # it isn't clear to me exactly what circumstances cause this).
1372 # `zipfile.write()` must be used directly to work around this.
1373 #
1374 # This mess can be avoided if we port to python3.
1375 saved_zip64_limit = zipfile.ZIP64_LIMIT
1376 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1377
1378 if compress_type is None:
1379 compress_type = zip_file.compression
1380 if arcname is None:
1381 arcname = filename
1382
1383 saved_stat = os.stat(filename)
1384
1385 try:
1386 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1387 # file to be zipped and reset it when we're done.
1388 os.chmod(filename, perms)
1389
1390 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001391 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1392 # intentional. zip stores datetimes in local time without a time zone
1393 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1394 # in the zip archive.
1395 local_epoch = datetime.datetime.fromtimestamp(0)
1396 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001397 os.utime(filename, (timestamp, timestamp))
1398
1399 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1400 finally:
1401 os.chmod(filename, saved_stat.st_mode)
1402 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1403 zipfile.ZIP64_LIMIT = saved_zip64_limit
1404
1405
Tao Bao58c1b962015-05-20 09:32:18 -07001406def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001407 compress_type=None):
1408 """Wrap zipfile.writestr() function to work around the zip64 limit.
1409
1410 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1411 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1412 when calling crc32(bytes).
1413
1414 But it still works fine to write a shorter string into a large zip file.
1415 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1416 when we know the string won't be too long.
1417 """
1418
1419 saved_zip64_limit = zipfile.ZIP64_LIMIT
1420 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1421
1422 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1423 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001424 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001425 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001426 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001427 else:
Tao Baof3282b42015-04-01 11:21:55 -07001428 zinfo = zinfo_or_arcname
1429
1430 # If compress_type is given, it overrides the value in zinfo.
1431 if compress_type is not None:
1432 zinfo.compress_type = compress_type
1433
Tao Bao58c1b962015-05-20 09:32:18 -07001434 # If perms is given, it has a priority.
1435 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001436 # If perms doesn't set the file type, mark it as a regular file.
1437 if perms & 0o770000 == 0:
1438 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001439 zinfo.external_attr = perms << 16
1440
Tao Baof3282b42015-04-01 11:21:55 -07001441 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001442 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1443
Dan Albert8b72aef2015-03-23 19:13:21 -07001444 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001445 zipfile.ZIP64_LIMIT = saved_zip64_limit
1446
1447
Tao Bao89d7ab22017-12-14 17:05:33 -08001448def ZipDelete(zip_filename, entries):
1449 """Deletes entries from a ZIP file.
1450
1451 Since deleting entries from a ZIP file is not supported, it shells out to
1452 'zip -d'.
1453
1454 Args:
1455 zip_filename: The name of the ZIP file.
1456 entries: The name of the entry, or the list of names to be deleted.
1457
1458 Raises:
1459 AssertionError: In case of non-zero return from 'zip'.
1460 """
1461 if isinstance(entries, basestring):
1462 entries = [entries]
1463 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001464 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001465
1466
Tao Baof3282b42015-04-01 11:21:55 -07001467def ZipClose(zip_file):
1468 # http://b/18015246
1469 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1470 # central directory.
1471 saved_zip64_limit = zipfile.ZIP64_LIMIT
1472 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1473
1474 zip_file.close()
1475
1476 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001477
1478
1479class DeviceSpecificParams(object):
1480 module = None
1481 def __init__(self, **kwargs):
1482 """Keyword arguments to the constructor become attributes of this
1483 object, which is passed to all functions in the device-specific
1484 module."""
1485 for k, v in kwargs.iteritems():
1486 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001487 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001488
1489 if self.module is None:
1490 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001491 if not path:
1492 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001493 try:
1494 if os.path.isdir(path):
1495 info = imp.find_module("releasetools", [path])
1496 else:
1497 d, f = os.path.split(path)
1498 b, x = os.path.splitext(f)
1499 if x == ".py":
1500 f = b
1501 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001502 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001503 self.module = imp.load_module("device_specific", *info)
1504 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001505 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001506
1507 def _DoCall(self, function_name, *args, **kwargs):
1508 """Call the named function in the device-specific module, passing
1509 the given args and kwargs. The first argument to the call will be
1510 the DeviceSpecific object itself. If there is no module, or the
1511 module does not define the function, return the value of the
1512 'default' kwarg (which itself defaults to None)."""
1513 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001514 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001515 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1516
1517 def FullOTA_Assertions(self):
1518 """Called after emitting the block of assertions at the top of a
1519 full OTA package. Implementations can add whatever additional
1520 assertions they like."""
1521 return self._DoCall("FullOTA_Assertions")
1522
Doug Zongkere5ff5902012-01-17 10:55:37 -08001523 def FullOTA_InstallBegin(self):
1524 """Called at the start of full OTA installation."""
1525 return self._DoCall("FullOTA_InstallBegin")
1526
Yifan Hong10c530d2018-12-27 17:34:18 -08001527 def FullOTA_GetBlockDifferences(self):
1528 """Called during full OTA installation and verification.
1529 Implementation should return a list of BlockDifference objects describing
1530 the update on each additional partitions.
1531 """
1532 return self._DoCall("FullOTA_GetBlockDifferences")
1533
Doug Zongker05d3dea2009-06-22 11:32:31 -07001534 def FullOTA_InstallEnd(self):
1535 """Called at the end of full OTA installation; typically this is
1536 used to install the image for the device's baseband processor."""
1537 return self._DoCall("FullOTA_InstallEnd")
1538
1539 def IncrementalOTA_Assertions(self):
1540 """Called after emitting the block of assertions at the top of an
1541 incremental OTA package. Implementations can add whatever
1542 additional assertions they like."""
1543 return self._DoCall("IncrementalOTA_Assertions")
1544
Doug Zongkere5ff5902012-01-17 10:55:37 -08001545 def IncrementalOTA_VerifyBegin(self):
1546 """Called at the start of the verification phase of incremental
1547 OTA installation; additional checks can be placed here to abort
1548 the script before any changes are made."""
1549 return self._DoCall("IncrementalOTA_VerifyBegin")
1550
Doug Zongker05d3dea2009-06-22 11:32:31 -07001551 def IncrementalOTA_VerifyEnd(self):
1552 """Called at the end of the verification phase of incremental OTA
1553 installation; additional checks can be placed here to abort the
1554 script before any changes are made."""
1555 return self._DoCall("IncrementalOTA_VerifyEnd")
1556
Doug Zongkere5ff5902012-01-17 10:55:37 -08001557 def IncrementalOTA_InstallBegin(self):
1558 """Called at the start of incremental OTA installation (after
1559 verification is complete)."""
1560 return self._DoCall("IncrementalOTA_InstallBegin")
1561
Yifan Hong10c530d2018-12-27 17:34:18 -08001562 def IncrementalOTA_GetBlockDifferences(self):
1563 """Called during incremental OTA installation and verification.
1564 Implementation should return a list of BlockDifference objects describing
1565 the update on each additional partitions.
1566 """
1567 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1568
Doug Zongker05d3dea2009-06-22 11:32:31 -07001569 def IncrementalOTA_InstallEnd(self):
1570 """Called at the end of incremental OTA installation; typically
1571 this is used to install the image for the device's baseband
1572 processor."""
1573 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001574
Tao Bao9bc6bb22015-11-09 16:58:28 -08001575 def VerifyOTA_Assertions(self):
1576 return self._DoCall("VerifyOTA_Assertions")
1577
Tao Bao76def242017-11-21 09:25:31 -08001578
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001579class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001580 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001581 self.name = name
1582 self.data = data
1583 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001584 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001585 self.sha1 = sha1(data).hexdigest()
1586
1587 @classmethod
1588 def FromLocalFile(cls, name, diskname):
1589 f = open(diskname, "rb")
1590 data = f.read()
1591 f.close()
1592 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001593
1594 def WriteToTemp(self):
1595 t = tempfile.NamedTemporaryFile()
1596 t.write(self.data)
1597 t.flush()
1598 return t
1599
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001600 def WriteToDir(self, d):
1601 with open(os.path.join(d, self.name), "wb") as fp:
1602 fp.write(self.data)
1603
Geremy Condra36bd3652014-02-06 19:45:10 -08001604 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001605 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001606
Tao Bao76def242017-11-21 09:25:31 -08001607
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001608DIFF_PROGRAM_BY_EXT = {
1609 ".gz" : "imgdiff",
1610 ".zip" : ["imgdiff", "-z"],
1611 ".jar" : ["imgdiff", "-z"],
1612 ".apk" : ["imgdiff", "-z"],
1613 ".img" : "imgdiff",
1614 }
1615
Tao Bao76def242017-11-21 09:25:31 -08001616
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001617class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001618 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001619 self.tf = tf
1620 self.sf = sf
1621 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001622 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001623
1624 def ComputePatch(self):
1625 """Compute the patch (as a string of data) needed to turn sf into
1626 tf. Returns the same tuple as GetPatch()."""
1627
1628 tf = self.tf
1629 sf = self.sf
1630
Doug Zongker24cd2802012-08-14 16:36:15 -07001631 if self.diff_program:
1632 diff_program = self.diff_program
1633 else:
1634 ext = os.path.splitext(tf.name)[1]
1635 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001636
1637 ttemp = tf.WriteToTemp()
1638 stemp = sf.WriteToTemp()
1639
1640 ext = os.path.splitext(tf.name)[1]
1641
1642 try:
1643 ptemp = tempfile.NamedTemporaryFile()
1644 if isinstance(diff_program, list):
1645 cmd = copy.copy(diff_program)
1646 else:
1647 cmd = [diff_program]
1648 cmd.append(stemp.name)
1649 cmd.append(ttemp.name)
1650 cmd.append(ptemp.name)
1651 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001652 err = []
1653 def run():
1654 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001655 if e:
1656 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001657 th = threading.Thread(target=run)
1658 th.start()
1659 th.join(timeout=300) # 5 mins
1660 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001661 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001662 p.terminate()
1663 th.join(5)
1664 if th.is_alive():
1665 p.kill()
1666 th.join()
1667
Tianjie Xua2a9f992018-01-05 15:15:54 -08001668 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001669 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001670 self.patch = None
1671 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001672 diff = ptemp.read()
1673 finally:
1674 ptemp.close()
1675 stemp.close()
1676 ttemp.close()
1677
1678 self.patch = diff
1679 return self.tf, self.sf, self.patch
1680
1681
1682 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001683 """Returns a tuple of (target_file, source_file, patch_data).
1684
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001685 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001686 computing the patch failed.
1687 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001688 return self.tf, self.sf, self.patch
1689
1690
1691def ComputeDifferences(diffs):
1692 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001693 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001694
1695 # Do the largest files first, to try and reduce the long-pole effect.
1696 by_size = [(i.tf.size, i) for i in diffs]
1697 by_size.sort(reverse=True)
1698 by_size = [i[1] for i in by_size]
1699
1700 lock = threading.Lock()
1701 diff_iter = iter(by_size) # accessed under lock
1702
1703 def worker():
1704 try:
1705 lock.acquire()
1706 for d in diff_iter:
1707 lock.release()
1708 start = time.time()
1709 d.ComputePatch()
1710 dur = time.time() - start
1711 lock.acquire()
1712
1713 tf, sf, patch = d.GetPatch()
1714 if sf.name == tf.name:
1715 name = tf.name
1716 else:
1717 name = "%s (%s)" % (tf.name, sf.name)
1718 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001719 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001720 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001721 logger.info(
1722 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1723 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001724 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001725 except Exception:
1726 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001727 raise
1728
1729 # start worker threads; wait for them all to finish.
1730 threads = [threading.Thread(target=worker)
1731 for i in range(OPTIONS.worker_threads)]
1732 for th in threads:
1733 th.start()
1734 while threads:
1735 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001736
1737
Dan Albert8b72aef2015-03-23 19:13:21 -07001738class BlockDifference(object):
1739 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001740 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001741 self.tgt = tgt
1742 self.src = src
1743 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001744 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001745 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001746
Tao Baodd2a5892015-03-12 12:32:37 -07001747 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001748 version = max(
1749 int(i) for i in
1750 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001751 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001752 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001753
1754 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001755 version=self.version,
1756 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001757 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001758 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001759 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001760 self.touched_src_ranges = b.touched_src_ranges
1761 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001762
Yifan Hong10c530d2018-12-27 17:34:18 -08001763 # On devices with dynamic partitions, for new partitions,
1764 # src is None but OPTIONS.source_info_dict is not.
1765 if OPTIONS.source_info_dict is None:
1766 is_dynamic_build = OPTIONS.info_dict.get(
1767 "use_dynamic_partitions") == "true"
Tao Baoaac4ad52015-10-16 15:26:34 -07001768 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001769 is_dynamic_build = OPTIONS.source_info_dict.get(
1770 "use_dynamic_partitions") == "true"
1771
1772 # For dynamic partitions builds, always check partition list in target build
1773 # because new partitions may be added.
1774 is_dynamic = is_dynamic_build and partition in shlex.split(
1775 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1776
1777 if is_dynamic:
1778 self.device = 'map_partition("%s")' % partition
1779 else:
1780 if OPTIONS.source_info_dict is None:
1781 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1782 else:
1783 _, device_path = GetTypeAndDevice("/" + partition,
1784 OPTIONS.source_info_dict)
1785 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001786
Tao Baod8d14be2016-02-04 14:26:02 -08001787 @property
1788 def required_cache(self):
1789 return self._required_cache
1790
Tao Bao76def242017-11-21 09:25:31 -08001791 def WriteScript(self, script, output_zip, progress=None,
1792 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001793 if not self.src:
1794 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001795 script.Print("Patching %s image unconditionally..." % (self.partition,))
1796 else:
1797 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001798
Dan Albert8b72aef2015-03-23 19:13:21 -07001799 if progress:
1800 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001801 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001802
1803 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001804 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001805
Tao Bao9bc6bb22015-11-09 16:58:28 -08001806 def WriteStrictVerifyScript(self, script):
1807 """Verify all the blocks in the care_map, including clobbered blocks.
1808
1809 This differs from the WriteVerifyScript() function: a) it prints different
1810 error messages; b) it doesn't allow half-way updated images to pass the
1811 verification."""
1812
1813 partition = self.partition
1814 script.Print("Verifying %s..." % (partition,))
1815 ranges = self.tgt.care_map
1816 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001817 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001818 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1819 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001820 self.device, ranges_str,
1821 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001822 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001823 script.AppendExtra("")
1824
Tao Baod522bdc2016-04-12 15:53:16 -07001825 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001826 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001827
1828 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001829 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001830 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001831
1832 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001833 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001834 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001835 ranges = self.touched_src_ranges
1836 expected_sha1 = self.touched_src_sha1
1837 else:
1838 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1839 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001840
1841 # No blocks to be checked, skipping.
1842 if not ranges:
1843 return
1844
Tao Bao5ece99d2015-05-12 11:42:31 -07001845 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001846 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001847 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001848 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1849 '"%s.patch.dat")) then' % (
1850 self.device, ranges_str, expected_sha1,
1851 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001852 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001853 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001854
Tianjie Xufc3422a2015-12-15 11:53:59 -08001855 if self.version >= 4:
1856
1857 # Bug: 21124327
1858 # When generating incrementals for the system and vendor partitions in
1859 # version 4 or newer, explicitly check the first block (which contains
1860 # the superblock) of the partition to see if it's what we expect. If
1861 # this check fails, give an explicit log message about the partition
1862 # having been remounted R/W (the most likely explanation).
1863 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08001864 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08001865
1866 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001867 if partition == "system":
1868 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1869 else:
1870 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001871 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08001872 'ifelse (block_image_recover({device}, "{ranges}") && '
1873 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001874 'package_extract_file("{partition}.transfer.list"), '
1875 '"{partition}.new.dat", "{partition}.patch.dat"), '
1876 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001877 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001878 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001879 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001880
Tao Baodd2a5892015-03-12 12:32:37 -07001881 # Abort the OTA update. Note that the incremental OTA cannot be applied
1882 # even if it may match the checksum of the target partition.
1883 # a) If version < 3, operations like move and erase will make changes
1884 # unconditionally and damage the partition.
1885 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001886 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001887 if partition == "system":
1888 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1889 else:
1890 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1891 script.AppendExtra((
1892 'abort("E%d: %s partition has unexpected contents");\n'
1893 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001894
Yifan Hong10c530d2018-12-27 17:34:18 -08001895 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07001896 partition = self.partition
1897 script.Print('Verifying the updated %s image...' % (partition,))
1898 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1899 ranges = self.tgt.care_map
1900 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001901 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001902 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001903 self.device, ranges_str,
1904 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001905
1906 # Bug: 20881595
1907 # Verify that extended blocks are really zeroed out.
1908 if self.tgt.extended:
1909 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001910 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001911 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001912 self.device, ranges_str,
1913 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001914 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001915 if partition == "system":
1916 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1917 else:
1918 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001919 script.AppendExtra(
1920 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001921 ' abort("E%d: %s partition has unexpected non-zero contents after '
1922 'OTA update");\n'
1923 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001924 else:
1925 script.Print('Verified the updated %s image.' % (partition,))
1926
Tianjie Xu209db462016-05-24 17:34:52 -07001927 if partition == "system":
1928 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1929 else:
1930 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1931
Tao Bao5fcaaef2015-06-01 13:40:49 -07001932 script.AppendExtra(
1933 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001934 ' abort("E%d: %s partition has unexpected contents after OTA '
1935 'update");\n'
1936 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001937
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001938 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001939 ZipWrite(output_zip,
1940 '{}.transfer.list'.format(self.path),
1941 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001942
Tao Bao76def242017-11-21 09:25:31 -08001943 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1944 # its size. Quailty 9 almost triples the compression time but doesn't
1945 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001946 # zip | brotli(quality 6) | brotli(quality 9)
1947 # compressed_size: 942M | 869M (~8% reduced) | 854M
1948 # compression_time: 75s | 265s | 719s
1949 # decompression_time: 15s | 25s | 25s
1950
1951 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001952 brotli_cmd = ['brotli', '--quality=6',
1953 '--output={}.new.dat.br'.format(self.path),
1954 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001955 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07001956 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001957
1958 new_data_name = '{}.new.dat.br'.format(self.partition)
1959 ZipWrite(output_zip,
1960 '{}.new.dat.br'.format(self.path),
1961 new_data_name,
1962 compress_type=zipfile.ZIP_STORED)
1963 else:
1964 new_data_name = '{}.new.dat'.format(self.partition)
1965 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1966
Dan Albert8e0178d2015-01-27 15:53:15 -08001967 ZipWrite(output_zip,
1968 '{}.patch.dat'.format(self.path),
1969 '{}.patch.dat'.format(self.partition),
1970 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001971
Tianjie Xu209db462016-05-24 17:34:52 -07001972 if self.partition == "system":
1973 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1974 else:
1975 code = ErrorCode.VENDOR_UPDATE_FAILURE
1976
Yifan Hong10c530d2018-12-27 17:34:18 -08001977 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08001978 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001979 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001980 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001981 device=self.device, partition=self.partition,
1982 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001983 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001984
Dan Albert8b72aef2015-03-23 19:13:21 -07001985 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001986 data = source.ReadRangeSet(ranges)
1987 ctx = sha1()
1988
1989 for p in data:
1990 ctx.update(p)
1991
1992 return ctx.hexdigest()
1993
Tao Baoe9b61912015-07-09 17:37:49 -07001994 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1995 """Return the hash value for all zero blocks."""
1996 zero_block = '\x00' * 4096
1997 ctx = sha1()
1998 for _ in range(num_blocks):
1999 ctx.update(zero_block)
2000
2001 return ctx.hexdigest()
2002
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002003
2004DataImage = blockimgdiff.DataImage
2005
Tao Bao76def242017-11-21 09:25:31 -08002006
Doug Zongker96a57e72010-09-26 14:57:41 -07002007# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002008PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002009 "ext4": "EMMC",
2010 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002011 "f2fs": "EMMC",
2012 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002013}
Doug Zongker96a57e72010-09-26 14:57:41 -07002014
Tao Bao76def242017-11-21 09:25:31 -08002015
Doug Zongker96a57e72010-09-26 14:57:41 -07002016def GetTypeAndDevice(mount_point, info):
2017 fstab = info["fstab"]
2018 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002019 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2020 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002021 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002022 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002023
2024
2025def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002026 """Parses and converts a PEM-encoded certificate into DER-encoded.
2027
2028 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2029
2030 Returns:
2031 The decoded certificate string.
2032 """
2033 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002034 save = False
2035 for line in data.split("\n"):
2036 if "--END CERTIFICATE--" in line:
2037 break
2038 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002039 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002040 if "--BEGIN CERTIFICATE--" in line:
2041 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002042 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002043 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002044
Tao Bao04e1f012018-02-04 12:13:35 -08002045
2046def ExtractPublicKey(cert):
2047 """Extracts the public key (PEM-encoded) from the given certificate file.
2048
2049 Args:
2050 cert: The certificate filename.
2051
2052 Returns:
2053 The public key string.
2054
2055 Raises:
2056 AssertionError: On non-zero return from 'openssl'.
2057 """
2058 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2059 # While openssl 1.1 writes the key into the given filename followed by '-out',
2060 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2061 # stdout instead.
2062 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2063 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2064 pubkey, stderrdata = proc.communicate()
2065 assert proc.returncode == 0, \
2066 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2067 return pubkey
2068
2069
Doug Zongker412c02f2014-02-13 10:58:24 -08002070def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2071 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002072 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002073
Tao Bao6d5d6232018-03-09 17:04:42 -08002074 Most of the space in the boot and recovery images is just the kernel, which is
2075 identical for the two, so the resulting patch should be efficient. Add it to
2076 the output zip, along with a shell script that is run from init.rc on first
2077 boot to actually do the patching and install the new recovery image.
2078
2079 Args:
2080 input_dir: The top-level input directory of the target-files.zip.
2081 output_sink: The callback function that writes the result.
2082 recovery_img: File object for the recovery image.
2083 boot_img: File objects for the boot image.
2084 info_dict: A dict returned by common.LoadInfoDict() on the input
2085 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002086 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002087 if info_dict is None:
2088 info_dict = OPTIONS.info_dict
2089
Tao Bao6d5d6232018-03-09 17:04:42 -08002090 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002091
Tao Baof2cffbd2015-07-22 12:33:18 -07002092 if full_recovery_image:
2093 output_sink("etc/recovery.img", recovery_img.data)
2094
2095 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002096 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002097 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002098 # With system-root-image, boot and recovery images will have mismatching
2099 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2100 # to handle such a case.
2101 if system_root_image:
2102 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002103 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002104 assert not os.path.exists(path)
2105 else:
2106 diff_program = ["imgdiff"]
2107 if os.path.exists(path):
2108 diff_program.append("-b")
2109 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002110 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002111 else:
2112 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002113
2114 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2115 _, _, patch = d.ComputePatch()
2116 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002117
Dan Albertebb19aa2015-03-27 19:11:53 -07002118 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002119 # The following GetTypeAndDevice()s need to use the path in the target
2120 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002121 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2122 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2123 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002124 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002125
Tao Baof2cffbd2015-07-22 12:33:18 -07002126 if full_recovery_image:
2127 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002128if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2129 applypatch \\
2130 --flash /system/etc/recovery.img \\
2131 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2132 log -t recovery "Installing new recovery image: succeeded" || \\
2133 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002134else
2135 log -t recovery "Recovery image already installed"
2136fi
2137""" % {'type': recovery_type,
2138 'device': recovery_device,
2139 'sha1': recovery_img.sha1,
2140 'size': recovery_img.size}
2141 else:
2142 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002143if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2144 applypatch %(bonus_args)s \\
2145 --patch /system/recovery-from-boot.p \\
2146 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2147 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2148 log -t recovery "Installing new recovery image: succeeded" || \\
2149 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002150else
2151 log -t recovery "Recovery image already installed"
2152fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002153""" % {'boot_size': boot_img.size,
2154 'boot_sha1': boot_img.sha1,
2155 'recovery_size': recovery_img.size,
2156 'recovery_sha1': recovery_img.sha1,
2157 'boot_type': boot_type,
2158 'boot_device': boot_device,
2159 'recovery_type': recovery_type,
2160 'recovery_device': recovery_device,
2161 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002162
2163 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002164 # in the L release.
2165 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002166
Tao Bao32fcdab2018-10-12 10:30:39 -07002167 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002168
2169 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002170
2171
2172class DynamicPartitionUpdate(object):
2173 def __init__(self, src_group=None, tgt_group=None, progress=None,
2174 block_difference=None):
2175 self.src_group = src_group
2176 self.tgt_group = tgt_group
2177 self.progress = progress
2178 self.block_difference = block_difference
2179
2180 @property
2181 def src_size(self):
2182 if not self.block_difference:
2183 return 0
2184 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2185
2186 @property
2187 def tgt_size(self):
2188 if not self.block_difference:
2189 return 0
2190 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2191
2192 @staticmethod
2193 def _GetSparseImageSize(img):
2194 if not img:
2195 return 0
2196 return img.blocksize * img.total_blocks
2197
2198
2199class DynamicGroupUpdate(object):
2200 def __init__(self, src_size=None, tgt_size=None):
2201 # None: group does not exist. 0: no size limits.
2202 self.src_size = src_size
2203 self.tgt_size = tgt_size
2204
2205
2206class DynamicPartitionsDifference(object):
2207 def __init__(self, info_dict, block_diffs, progress_dict=None,
2208 source_info_dict=None):
2209 if progress_dict is None:
2210 progress_dict = dict()
2211
2212 self._remove_all_before_apply = False
2213 if source_info_dict is None:
2214 self._remove_all_before_apply = True
2215 source_info_dict = dict()
2216
2217 block_diff_dict = {e.partition:e for e in block_diffs}
2218 assert len(block_diff_dict) == len(block_diffs), \
2219 "Duplicated BlockDifference object for {}".format(
2220 [partition for partition, count in
2221 collections.Counter(e.partition for e in block_diffs).items()
2222 if count > 1])
2223
2224 dynamic_partitions = set(shlex.split(info_dict.get(
2225 "dynamic_partition_list", "").strip()))
2226 assert set(block_diff_dict.keys()) == dynamic_partitions, \
2227 "Dynamic partitions: {}, BlockDifference objects: {}".format(
2228 list(dynamic_partitions), list(block_diff_dict.keys()))
2229
2230 self._partition_updates = dict()
2231
2232 for p, block_diff in block_diff_dict.items():
2233 self._partition_updates[p] = DynamicPartitionUpdate()
2234 self._partition_updates[p].block_difference = block_diff
2235
2236 for p, progress in progress_dict.items():
2237 if p in self._partition_updates:
2238 self._partition_updates[p].progress = progress
2239
2240 tgt_groups = shlex.split(info_dict.get(
2241 "super_partition_groups", "").strip())
2242 src_groups = shlex.split(source_info_dict.get(
2243 "super_partition_groups", "").strip())
2244
2245 for g in tgt_groups:
2246 for p in shlex.split(info_dict.get(
2247 "super_%s_partition_list" % g, "").strip()):
2248 assert p in self._partition_updates, \
2249 "{} is in target super_{}_partition_list but no BlockDifference " \
2250 "object is provided.".format(p, g)
2251 self._partition_updates[p].tgt_group = g
2252
2253 for g in src_groups:
2254 for p in shlex.split(source_info_dict.get(
2255 "super_%s_partition_list" % g, "").strip()):
2256 assert p in self._partition_updates, \
2257 "{} is in source super_{}_partition_list but no BlockDifference " \
2258 "object is provided.".format(p, g)
2259 self._partition_updates[p].src_group = g
2260
2261 if self._partition_updates:
2262 logger.info("Updating dynamic partitions %s",
2263 self._partition_updates.keys())
2264
2265 self._group_updates = dict()
2266
2267 for g in tgt_groups:
2268 self._group_updates[g] = DynamicGroupUpdate()
2269 self._group_updates[g].tgt_size = int(info_dict.get(
2270 "super_%s_group_size" % g, "0").strip())
2271
2272 for g in src_groups:
2273 if g not in self._group_updates:
2274 self._group_updates[g] = DynamicGroupUpdate()
2275 self._group_updates[g].src_size = int(source_info_dict.get(
2276 "super_%s_group_size" % g, "0").strip())
2277
2278 self._Compute()
2279
2280 def WriteScript(self, script, output_zip, write_verify_script=False):
2281 script.Comment('--- Start patching dynamic partitions ---')
2282 for p, u in self._partition_updates.items():
2283 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2284 script.Comment('Patch partition %s' % p)
2285 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2286 write_verify_script=False)
2287
2288 op_list_path = MakeTempFile()
2289 with open(op_list_path, 'w') as f:
2290 for line in self._op_list:
2291 f.write('{}\n'.format(line))
2292
2293 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2294
2295 script.Comment('Update dynamic partition metadata')
2296 script.AppendExtra('assert(update_dynamic_partitions('
2297 'package_extract_file("dynamic_partitions_op_list")));')
2298
2299 if write_verify_script:
2300 for p, u in self._partition_updates.items():
2301 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2302 u.block_difference.WritePostInstallVerifyScript(script)
2303 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2304
2305 for p, u in self._partition_updates.items():
2306 if u.tgt_size and u.src_size <= u.tgt_size:
2307 script.Comment('Patch partition %s' % p)
2308 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2309 write_verify_script=write_verify_script)
2310 if write_verify_script:
2311 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2312
2313 script.Comment('--- End patching dynamic partitions ---')
2314
2315 def _Compute(self):
2316 self._op_list = list()
2317
2318 def append(line):
2319 self._op_list.append(line)
2320
2321 def comment(line):
2322 self._op_list.append("# %s" % line)
2323
2324 if self._remove_all_before_apply:
2325 comment('Remove all existing dynamic partitions and groups before '
2326 'applying full OTA')
2327 append('remove_all_groups')
2328
2329 for p, u in self._partition_updates.items():
2330 if u.src_group and not u.tgt_group:
2331 append('remove %s' % p)
2332
2333 for p, u in self._partition_updates.items():
2334 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2335 comment('Move partition %s from %s to default' % (p, u.src_group))
2336 append('move %s default' % p)
2337
2338 for p, u in self._partition_updates.items():
2339 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2340 comment('Shrink partition %s from %d to %d' %
2341 (p, u.src_size, u.tgt_size))
2342 append('resize %s %s' % (p, u.tgt_size))
2343
2344 for g, u in self._group_updates.items():
2345 if u.src_size is not None and u.tgt_size is None:
2346 append('remove_group %s' % g)
2347 if (u.src_size is not None and u.tgt_size is not None and
2348 u.src_size > u.tgt_size):
2349 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2350 append('resize_group %s %d' % (g, u.tgt_size))
2351
2352 for g, u in self._group_updates.items():
2353 if u.src_size is None and u.tgt_size is not None:
2354 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2355 append('add_group %s %d' % (g, u.tgt_size))
2356 if (u.src_size is not None and u.tgt_size is not None and
2357 u.src_size < u.tgt_size):
2358 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2359 append('resize_group %s %d' % (g, u.tgt_size))
2360
2361 for p, u in self._partition_updates.items():
2362 if u.tgt_group and not u.src_group:
2363 comment('Add partition %s to group %s' % (p, u.tgt_group))
2364 append('add %s %s' % (p, u.tgt_group))
2365
2366 for p, u in self._partition_updates.items():
2367 if u.tgt_size and u.src_size < u.tgt_size:
2368 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2369 append('resize %s %d' % (p, u.tgt_size))
2370
2371 for p, u in self._partition_updates.items():
2372 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2373 comment('Move partition %s from default to %s' %
2374 (p, u.tgt_group))
2375 append('move %s %s' % (p, u.tgt_group))