blob: 04158943d48622762b59f1b1af29d76bb352a601 [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
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800591 fn = os.path.join(sourcedir, "dtb")
592 if os.access(fn, os.F_OK):
593 cmd.append("--dtb")
594 cmd.append(fn)
595
Doug Zongker171f1cd2009-06-15 22:36:37 -0700596 fn = os.path.join(sourcedir, "cmdline")
597 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700598 cmd.append("--cmdline")
599 cmd.append(open(fn).read().rstrip("\n"))
600
601 fn = os.path.join(sourcedir, "base")
602 if os.access(fn, os.F_OK):
603 cmd.append("--base")
604 cmd.append(open(fn).read().rstrip("\n"))
605
Ying Wang4de6b5b2010-08-25 14:29:34 -0700606 fn = os.path.join(sourcedir, "pagesize")
607 if os.access(fn, os.F_OK):
608 cmd.append("--pagesize")
609 cmd.append(open(fn).read().rstrip("\n"))
610
Tao Bao76def242017-11-21 09:25:31 -0800611 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700612 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700613 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700614
Tao Bao76def242017-11-21 09:25:31 -0800615 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000616 if args and args.strip():
617 cmd.extend(shlex.split(args))
618
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700619 if has_ramdisk:
620 cmd.extend(["--ramdisk", ramdisk_img.name])
621
Tao Baod95e9fd2015-03-29 23:07:41 -0700622 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800623 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700624 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700625 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700626 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700627 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700628
Tao Baobf70c3182017-07-11 17:27:55 -0700629 # "boot" or "recovery", without extension.
630 partition_name = os.path.basename(sourcedir).lower()
631
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800632 if partition_name == "recovery":
633 if info_dict.get("include_recovery_dtbo") == "true":
634 fn = os.path.join(sourcedir, "recovery_dtbo")
635 cmd.extend(["--recovery_dtbo", fn])
636 if info_dict.get("include_recovery_acpio") == "true":
637 fn = os.path.join(sourcedir, "recovery_acpio")
638 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700639
Tao Bao986ee862018-10-04 15:46:16 -0700640 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700641
Tao Bao76def242017-11-21 09:25:31 -0800642 if (info_dict.get("boot_signer") == "true" and
643 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800644 # Hard-code the path as "/boot" for two-step special recovery image (which
645 # will be loaded into /boot during the two-step OTA).
646 if two_step_image:
647 path = "/boot"
648 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700649 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700650 cmd = [OPTIONS.boot_signer_path]
651 cmd.extend(OPTIONS.boot_signer_args)
652 cmd.extend([path, img.name,
653 info_dict["verity_key"] + ".pk8",
654 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700655 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700656
Tao Baod95e9fd2015-03-29 23:07:41 -0700657 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800658 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700659 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700660 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800661 # We have switched from the prebuilt futility binary to using the tool
662 # (futility-host) built from the source. Override the setting in the old
663 # TF.zip.
664 futility = info_dict["futility"]
665 if futility.startswith("prebuilts/"):
666 futility = "futility-host"
667 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700668 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700669 info_dict["vboot_key"] + ".vbprivk",
670 info_dict["vboot_subkey"] + ".vbprivk",
671 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700672 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700673 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700674
Tao Baof3282b42015-04-01 11:21:55 -0700675 # Clean up the temp files.
676 img_unsigned.close()
677 img_keyblock.close()
678
David Zeuthen8fecb282017-12-01 16:24:01 -0500679 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800680 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700681 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500682 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400683 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700684 "--partition_size", str(part_size), "--partition_name",
685 partition_name]
686 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500687 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400688 if args and args.strip():
689 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700690 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500691
692 img.seek(os.SEEK_SET, 0)
693 data = img.read()
694
695 if has_ramdisk:
696 ramdisk_img.close()
697 img.close()
698
699 return data
700
701
Doug Zongkerd5131602012-08-02 14:46:42 -0700702def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800703 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700704 """Return a File object with the desired bootable image.
705
706 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
707 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
708 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700709
Doug Zongker55d93282011-01-25 17:03:34 -0800710 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
711 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700712 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800713 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700714
715 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
716 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700717 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700718 return File.FromLocalFile(name, prebuilt_path)
719
Tao Bao32fcdab2018-10-12 10:30:39 -0700720 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700721
722 if info_dict is None:
723 info_dict = OPTIONS.info_dict
724
725 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800726 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
727 # for recovery.
728 has_ramdisk = (info_dict.get("system_root_image") != "true" or
729 prebuilt_name != "boot.img" or
730 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700731
Doug Zongker6f1d0312014-08-22 08:07:12 -0700732 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400733 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
734 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800735 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700736 if data:
737 return File(name, data)
738 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800739
Doug Zongkereef39442009-04-02 12:14:19 -0700740
Narayan Kamatha07bf042017-08-14 14:49:21 +0100741def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800742 """Gunzips the given gzip compressed file to a given output file."""
743 with gzip.open(in_filename, "rb") as in_file, \
744 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100745 shutil.copyfileobj(in_file, out_file)
746
747
Doug Zongker75f17362009-12-08 13:46:44 -0800748def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800749 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800750
Tao Bao1c830bf2017-12-25 10:43:47 -0800751 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
752 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800753
Tao Bao1c830bf2017-12-25 10:43:47 -0800754 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800755 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800756 """
Doug Zongkereef39442009-04-02 12:14:19 -0700757
Doug Zongker55d93282011-01-25 17:03:34 -0800758 def unzip_to_dir(filename, dirname):
759 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
760 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800761 cmd.extend(pattern)
Tao Bao986ee862018-10-04 15:46:16 -0700762 RunAndCheckOutput(cmd)
Doug Zongker55d93282011-01-25 17:03:34 -0800763
Tao Bao1c830bf2017-12-25 10:43:47 -0800764 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800765 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
766 if m:
767 unzip_to_dir(m.group(1), tmp)
768 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
769 filename = m.group(1)
770 else:
771 unzip_to_dir(filename, tmp)
772
Tao Baodba59ee2018-01-09 13:21:02 -0800773 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700774
775
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700776def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
777 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800778 """Returns a SparseImage object suitable for passing to BlockImageDiff.
779
780 This function loads the specified sparse image from the given path, and
781 performs additional processing for OTA purpose. For example, it always adds
782 block 0 to clobbered blocks list. It also detects files that cannot be
783 reconstructed from the block list, for whom we should avoid applying imgdiff.
784
785 Args:
786 which: The partition name, which must be "system" or "vendor".
787 tmpdir: The directory that contains the prebuilt image and block map file.
788 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800789 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700790 hashtree_info_generator: If present, generates the hashtree_info for this
791 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800792 Returns:
793 A SparseImage object, with file_map info loaded.
794 """
795 assert which in ("system", "vendor")
796
797 path = os.path.join(tmpdir, "IMAGES", which + ".img")
798 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
799
800 # The image and map files must have been created prior to calling
801 # ota_from_target_files.py (since LMP).
802 assert os.path.exists(path) and os.path.exists(mappath)
803
804 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
805 # it to clobbered_blocks so that it will be written to the target
806 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
807 clobbered_blocks = "0"
808
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700809 image = sparse_img.SparseImage(
810 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
811 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800812
813 # block.map may contain less blocks, because mke2fs may skip allocating blocks
814 # if they contain all zeros. We can't reconstruct such a file from its block
815 # list. Tag such entries accordingly. (Bug: 65213616)
816 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800817 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700818 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800819 continue
820
Tom Cherryd14b8952018-08-09 14:26:00 -0700821 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
822 # filename listed in system.map may contain an additional leading slash
823 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
824 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700825 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
826
Tom Cherryd14b8952018-08-09 14:26:00 -0700827 # Special handling another case, where files not under /system
828 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700829 if which == 'system' and not arcname.startswith('SYSTEM'):
830 arcname = 'ROOT/' + arcname
831
832 assert arcname in input_zip.namelist(), \
833 "Failed to find the ZIP entry for {}".format(entry)
834
Tao Baoc765cca2018-01-31 17:32:40 -0800835 info = input_zip.getinfo(arcname)
836 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800837
838 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800839 # image, check the original block list to determine its completeness. Note
840 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800841 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800842 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800843
Tao Baoc765cca2018-01-31 17:32:40 -0800844 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
845 ranges.extra['incomplete'] = True
846
847 return image
848
849
Doug Zongkereef39442009-04-02 12:14:19 -0700850def GetKeyPasswords(keylist):
851 """Given a list of keys, prompt the user to enter passwords for
852 those which require them. Return a {key: password} dict. password
853 will be None if the key has no password."""
854
Doug Zongker8ce7c252009-05-22 13:34:54 -0700855 no_passwords = []
856 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700857 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700858 devnull = open("/dev/null", "w+b")
859 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800860 # We don't need a password for things that aren't really keys.
861 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700862 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700863 continue
864
T.R. Fullhart37e10522013-03-18 10:31:26 -0700865 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700866 "-inform", "DER", "-nocrypt"],
867 stdin=devnull.fileno(),
868 stdout=devnull.fileno(),
869 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700870 p.communicate()
871 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700872 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700873 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700874 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700875 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
876 "-inform", "DER", "-passin", "pass:"],
877 stdin=devnull.fileno(),
878 stdout=devnull.fileno(),
879 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700880 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700881 if p.returncode == 0:
882 # Encrypted key with empty string as password.
883 key_passwords[k] = ''
884 elif stderr.startswith('Error decrypting key'):
885 # Definitely encrypted key.
886 # It would have said "Error reading key" if it didn't parse correctly.
887 need_passwords.append(k)
888 else:
889 # Potentially, a type of key that openssl doesn't understand.
890 # We'll let the routines in signapk.jar handle it.
891 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700892 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700893
T.R. Fullhart37e10522013-03-18 10:31:26 -0700894 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800895 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700896 return key_passwords
897
898
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800899def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700900 """Gets the minSdkVersion declared in the APK.
901
902 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
903 This can be both a decimal number (API Level) or a codename.
904
905 Args:
906 apk_name: The APK filename.
907
908 Returns:
909 The parsed SDK version string.
910
911 Raises:
912 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800913 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700914 proc = Run(
915 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
916 stderr=subprocess.PIPE)
917 stdoutdata, stderrdata = proc.communicate()
918 if proc.returncode != 0:
919 raise ExternalError(
920 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
921 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800922
Tao Baof47bf0f2018-03-21 23:28:51 -0700923 for line in stdoutdata.split("\n"):
924 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800925 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
926 if m:
927 return m.group(1)
928 raise ExternalError("No minSdkVersion returned by aapt")
929
930
931def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700932 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800933
Tao Baof47bf0f2018-03-21 23:28:51 -0700934 If minSdkVersion is set to a codename, it is translated to a number using the
935 provided map.
936
937 Args:
938 apk_name: The APK filename.
939
940 Returns:
941 The parsed SDK version number.
942
943 Raises:
944 ExternalError: On failing to get the min SDK version number.
945 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800946 version = GetMinSdkVersion(apk_name)
947 try:
948 return int(version)
949 except ValueError:
950 # Not a decimal number. Codename?
951 if version in codename_to_api_level_map:
952 return codename_to_api_level_map[version]
953 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700954 raise ExternalError(
955 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
956 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800957
958
959def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800960 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700961 """Sign the input_name zip/jar/apk, producing output_name. Use the
962 given key and password (the latter may be None if the key does not
963 have a password.
964
Doug Zongker951495f2009-08-14 12:44:19 -0700965 If whole_file is true, use the "-w" option to SignApk to embed a
966 signature that covers the whole file in the archive comment of the
967 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800968
969 min_api_level is the API Level (int) of the oldest platform this file may end
970 up on. If not specified for an APK, the API Level is obtained by interpreting
971 the minSdkVersion attribute of the APK's AndroidManifest.xml.
972
973 codename_to_api_level_map is needed to translate the codename which may be
974 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700975 """
Tao Bao76def242017-11-21 09:25:31 -0800976 if codename_to_api_level_map is None:
977 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700978
Alex Klyubin9667b182015-12-10 13:38:50 -0800979 java_library_path = os.path.join(
980 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
981
Tao Baoe95540e2016-11-08 12:08:53 -0800982 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
983 ["-Djava.library.path=" + java_library_path,
984 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
985 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700986 if whole_file:
987 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800988
989 min_sdk_version = min_api_level
990 if min_sdk_version is None:
991 if not whole_file:
992 min_sdk_version = GetMinSdkVersionInt(
993 input_name, codename_to_api_level_map)
994 if min_sdk_version is not None:
995 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
996
T.R. Fullhart37e10522013-03-18 10:31:26 -0700997 cmd.extend([key + OPTIONS.public_key_suffix,
998 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800999 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -07001000
Tao Bao73dd4f42018-10-04 16:25:33 -07001001 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001002 if password is not None:
1003 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001004 stdoutdata, _ = proc.communicate(password)
1005 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001006 raise ExternalError(
1007 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001008 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001009
Doug Zongkereef39442009-04-02 12:14:19 -07001010
Doug Zongker37974732010-09-16 17:44:38 -07001011def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001012 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001013
Tao Bao9dd909e2017-11-14 11:27:32 -08001014 For non-AVB images, raise exception if the data is too big. Print a warning
1015 if the data is nearing the maximum size.
1016
1017 For AVB images, the actual image size should be identical to the limit.
1018
1019 Args:
1020 data: A string that contains all the data for the partition.
1021 target: The partition name. The ".img" suffix is optional.
1022 info_dict: The dict to be looked up for relevant info.
1023 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001024 if target.endswith(".img"):
1025 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001026 mount_point = "/" + target
1027
Ying Wangf8824af2014-06-03 14:07:27 -07001028 fs_type = None
1029 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001030 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001031 if mount_point == "/userdata":
1032 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001033 p = info_dict["fstab"][mount_point]
1034 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001035 device = p.device
1036 if "/" in device:
1037 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001038 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001039 if not fs_type or not limit:
1040 return
Doug Zongkereef39442009-04-02 12:14:19 -07001041
Andrew Boie0f9aec82012-02-14 09:32:52 -08001042 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001043 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1044 # path.
1045 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1046 if size != limit:
1047 raise ExternalError(
1048 "Mismatching image size for %s: expected %d actual %d" % (
1049 target, limit, size))
1050 else:
1051 pct = float(size) * 100.0 / limit
1052 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1053 if pct >= 99.0:
1054 raise ExternalError(msg)
1055 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001056 logger.warning("\n WARNING: %s\n", msg)
1057 else:
1058 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001059
1060
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001061def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001062 """Parses the APK certs info from a given target-files zip.
1063
1064 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1065 tuple with the following elements: (1) a dictionary that maps packages to
1066 certs (based on the "certificate" and "private_key" attributes in the file;
1067 (2) a string representing the extension of compressed APKs in the target files
1068 (e.g ".gz", ".bro").
1069
1070 Args:
1071 tf_zip: The input target_files ZipFile (already open).
1072
1073 Returns:
1074 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1075 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1076 no compressed APKs.
1077 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001078 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001079 compressed_extension = None
1080
Tao Bao0f990332017-09-08 19:02:54 -07001081 # META/apkcerts.txt contains the info for _all_ the packages known at build
1082 # time. Filter out the ones that are not installed.
1083 installed_files = set()
1084 for name in tf_zip.namelist():
1085 basename = os.path.basename(name)
1086 if basename:
1087 installed_files.add(basename)
1088
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001089 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1090 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001091 if not line:
1092 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001093 m = re.match(
1094 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1095 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1096 line)
1097 if not m:
1098 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001099
Tao Bao818ddf52018-01-05 11:17:34 -08001100 matches = m.groupdict()
1101 cert = matches["CERT"]
1102 privkey = matches["PRIVKEY"]
1103 name = matches["NAME"]
1104 this_compressed_extension = matches["COMPRESSED"]
1105
1106 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1107 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1108 if cert in SPECIAL_CERT_STRINGS and not privkey:
1109 certmap[name] = cert
1110 elif (cert.endswith(OPTIONS.public_key_suffix) and
1111 privkey.endswith(OPTIONS.private_key_suffix) and
1112 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1113 certmap[name] = cert[:-public_key_suffix_len]
1114 else:
1115 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1116
1117 if not this_compressed_extension:
1118 continue
1119
1120 # Only count the installed files.
1121 filename = name + '.' + this_compressed_extension
1122 if filename not in installed_files:
1123 continue
1124
1125 # Make sure that all the values in the compression map have the same
1126 # extension. We don't support multiple compression methods in the same
1127 # system image.
1128 if compressed_extension:
1129 if this_compressed_extension != compressed_extension:
1130 raise ValueError(
1131 "Multiple compressed extensions: {} vs {}".format(
1132 compressed_extension, this_compressed_extension))
1133 else:
1134 compressed_extension = this_compressed_extension
1135
1136 return (certmap,
1137 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001138
1139
Doug Zongkereef39442009-04-02 12:14:19 -07001140COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001141Global options
1142
1143 -p (--path) <dir>
1144 Prepend <dir>/bin to the list of places to search for binaries run by this
1145 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001146
Doug Zongker05d3dea2009-06-22 11:32:31 -07001147 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001148 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001149
Tao Bao30df8b42018-04-23 15:32:53 -07001150 -x (--extra) <key=value>
1151 Add a key/value pair to the 'extras' dict, which device-specific extension
1152 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001153
Doug Zongkereef39442009-04-02 12:14:19 -07001154 -v (--verbose)
1155 Show command lines being executed.
1156
1157 -h (--help)
1158 Display this usage message and exit.
1159"""
1160
1161def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001162 print(docstring.rstrip("\n"))
1163 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001164
1165
1166def ParseOptions(argv,
1167 docstring,
1168 extra_opts="", extra_long_opts=(),
1169 extra_option_handler=None):
1170 """Parse the options in argv and return any arguments that aren't
1171 flags. docstring is the calling module's docstring, to be displayed
1172 for errors and -h. extra_opts and extra_long_opts are for flags
1173 defined by the caller, which are processed by passing them to
1174 extra_option_handler."""
1175
1176 try:
1177 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001178 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001179 ["help", "verbose", "path=", "signapk_path=",
1180 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001181 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001182 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1183 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001184 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001185 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001186 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001187 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001188 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001189 sys.exit(2)
1190
Doug Zongkereef39442009-04-02 12:14:19 -07001191 for o, a in opts:
1192 if o in ("-h", "--help"):
1193 Usage(docstring)
1194 sys.exit()
1195 elif o in ("-v", "--verbose"):
1196 OPTIONS.verbose = True
1197 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001198 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001199 elif o in ("--signapk_path",):
1200 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001201 elif o in ("--signapk_shared_library_path",):
1202 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001203 elif o in ("--extra_signapk_args",):
1204 OPTIONS.extra_signapk_args = shlex.split(a)
1205 elif o in ("--java_path",):
1206 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001207 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001208 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001209 elif o in ("--public_key_suffix",):
1210 OPTIONS.public_key_suffix = a
1211 elif o in ("--private_key_suffix",):
1212 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001213 elif o in ("--boot_signer_path",):
1214 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001215 elif o in ("--boot_signer_args",):
1216 OPTIONS.boot_signer_args = shlex.split(a)
1217 elif o in ("--verity_signer_path",):
1218 OPTIONS.verity_signer_path = a
1219 elif o in ("--verity_signer_args",):
1220 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001221 elif o in ("-s", "--device_specific"):
1222 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001223 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001224 key, value = a.split("=", 1)
1225 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001226 else:
1227 if extra_option_handler is None or not extra_option_handler(o, a):
1228 assert False, "unknown option \"%s\"" % (o,)
1229
Doug Zongker85448772014-09-09 14:59:20 -07001230 if OPTIONS.search_path:
1231 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1232 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001233
1234 return args
1235
1236
Tao Bao4c851b12016-09-19 13:54:38 -07001237def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001238 """Make a temp file and add it to the list of things to be deleted
1239 when Cleanup() is called. Return the filename."""
1240 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1241 os.close(fd)
1242 OPTIONS.tempfiles.append(fn)
1243 return fn
1244
1245
Tao Bao1c830bf2017-12-25 10:43:47 -08001246def MakeTempDir(prefix='tmp', suffix=''):
1247 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1248
1249 Returns:
1250 The absolute pathname of the new directory.
1251 """
1252 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1253 OPTIONS.tempfiles.append(dir_name)
1254 return dir_name
1255
1256
Doug Zongkereef39442009-04-02 12:14:19 -07001257def Cleanup():
1258 for i in OPTIONS.tempfiles:
1259 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001260 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001261 else:
1262 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001263 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001264
1265
1266class PasswordManager(object):
1267 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001268 self.editor = os.getenv("EDITOR")
1269 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001270
1271 def GetPasswords(self, items):
1272 """Get passwords corresponding to each string in 'items',
1273 returning a dict. (The dict may have keys in addition to the
1274 values in 'items'.)
1275
1276 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1277 user edit that file to add more needed passwords. If no editor is
1278 available, or $ANDROID_PW_FILE isn't define, prompts the user
1279 interactively in the ordinary way.
1280 """
1281
1282 current = self.ReadFile()
1283
1284 first = True
1285 while True:
1286 missing = []
1287 for i in items:
1288 if i not in current or not current[i]:
1289 missing.append(i)
1290 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001291 if not missing:
1292 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001293
1294 for i in missing:
1295 current[i] = ""
1296
1297 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001298 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001299 answer = raw_input("try to edit again? [y]> ").strip()
1300 if answer and answer[0] not in 'yY':
1301 raise RuntimeError("key passwords unavailable")
1302 first = False
1303
1304 current = self.UpdateAndReadFile(current)
1305
Dan Albert8b72aef2015-03-23 19:13:21 -07001306 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001307 """Prompt the user to enter a value (password) for each key in
1308 'current' whose value is fales. Returns a new dict with all the
1309 values.
1310 """
1311 result = {}
1312 for k, v in sorted(current.iteritems()):
1313 if v:
1314 result[k] = v
1315 else:
1316 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001317 result[k] = getpass.getpass(
1318 "Enter password for %s key> " % k).strip()
1319 if result[k]:
1320 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001321 return result
1322
1323 def UpdateAndReadFile(self, current):
1324 if not self.editor or not self.pwfile:
1325 return self.PromptResult(current)
1326
1327 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001328 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001329 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1330 f.write("# (Additional spaces are harmless.)\n\n")
1331
1332 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001333 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1334 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001335 f.write("[[[ %s ]]] %s\n" % (v, k))
1336 if not v and first_line is None:
1337 # position cursor on first line with no password.
1338 first_line = i + 4
1339 f.close()
1340
Tao Bao986ee862018-10-04 15:46:16 -07001341 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001342
1343 return self.ReadFile()
1344
1345 def ReadFile(self):
1346 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001347 if self.pwfile is None:
1348 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001349 try:
1350 f = open(self.pwfile, "r")
1351 for line in f:
1352 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001353 if not line or line[0] == '#':
1354 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001355 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1356 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001357 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001358 else:
1359 result[m.group(2)] = m.group(1)
1360 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001361 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001362 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001363 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001364 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001365
1366
Dan Albert8e0178d2015-01-27 15:53:15 -08001367def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1368 compress_type=None):
1369 import datetime
1370
1371 # http://b/18015246
1372 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1373 # for files larger than 2GiB. We can work around this by adjusting their
1374 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1375 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1376 # it isn't clear to me exactly what circumstances cause this).
1377 # `zipfile.write()` must be used directly to work around this.
1378 #
1379 # This mess can be avoided if we port to python3.
1380 saved_zip64_limit = zipfile.ZIP64_LIMIT
1381 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1382
1383 if compress_type is None:
1384 compress_type = zip_file.compression
1385 if arcname is None:
1386 arcname = filename
1387
1388 saved_stat = os.stat(filename)
1389
1390 try:
1391 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1392 # file to be zipped and reset it when we're done.
1393 os.chmod(filename, perms)
1394
1395 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001396 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1397 # intentional. zip stores datetimes in local time without a time zone
1398 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1399 # in the zip archive.
1400 local_epoch = datetime.datetime.fromtimestamp(0)
1401 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001402 os.utime(filename, (timestamp, timestamp))
1403
1404 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1405 finally:
1406 os.chmod(filename, saved_stat.st_mode)
1407 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1408 zipfile.ZIP64_LIMIT = saved_zip64_limit
1409
1410
Tao Bao58c1b962015-05-20 09:32:18 -07001411def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001412 compress_type=None):
1413 """Wrap zipfile.writestr() function to work around the zip64 limit.
1414
1415 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1416 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1417 when calling crc32(bytes).
1418
1419 But it still works fine to write a shorter string into a large zip file.
1420 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1421 when we know the string won't be too long.
1422 """
1423
1424 saved_zip64_limit = zipfile.ZIP64_LIMIT
1425 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1426
1427 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1428 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001429 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001430 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001431 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001432 else:
Tao Baof3282b42015-04-01 11:21:55 -07001433 zinfo = zinfo_or_arcname
1434
1435 # If compress_type is given, it overrides the value in zinfo.
1436 if compress_type is not None:
1437 zinfo.compress_type = compress_type
1438
Tao Bao58c1b962015-05-20 09:32:18 -07001439 # If perms is given, it has a priority.
1440 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001441 # If perms doesn't set the file type, mark it as a regular file.
1442 if perms & 0o770000 == 0:
1443 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001444 zinfo.external_attr = perms << 16
1445
Tao Baof3282b42015-04-01 11:21:55 -07001446 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001447 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1448
Dan Albert8b72aef2015-03-23 19:13:21 -07001449 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001450 zipfile.ZIP64_LIMIT = saved_zip64_limit
1451
1452
Tao Bao89d7ab22017-12-14 17:05:33 -08001453def ZipDelete(zip_filename, entries):
1454 """Deletes entries from a ZIP file.
1455
1456 Since deleting entries from a ZIP file is not supported, it shells out to
1457 'zip -d'.
1458
1459 Args:
1460 zip_filename: The name of the ZIP file.
1461 entries: The name of the entry, or the list of names to be deleted.
1462
1463 Raises:
1464 AssertionError: In case of non-zero return from 'zip'.
1465 """
1466 if isinstance(entries, basestring):
1467 entries = [entries]
1468 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001469 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001470
1471
Tao Baof3282b42015-04-01 11:21:55 -07001472def ZipClose(zip_file):
1473 # http://b/18015246
1474 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1475 # central directory.
1476 saved_zip64_limit = zipfile.ZIP64_LIMIT
1477 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1478
1479 zip_file.close()
1480
1481 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001482
1483
1484class DeviceSpecificParams(object):
1485 module = None
1486 def __init__(self, **kwargs):
1487 """Keyword arguments to the constructor become attributes of this
1488 object, which is passed to all functions in the device-specific
1489 module."""
1490 for k, v in kwargs.iteritems():
1491 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001492 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001493
1494 if self.module is None:
1495 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001496 if not path:
1497 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001498 try:
1499 if os.path.isdir(path):
1500 info = imp.find_module("releasetools", [path])
1501 else:
1502 d, f = os.path.split(path)
1503 b, x = os.path.splitext(f)
1504 if x == ".py":
1505 f = b
1506 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001507 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001508 self.module = imp.load_module("device_specific", *info)
1509 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001510 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001511
1512 def _DoCall(self, function_name, *args, **kwargs):
1513 """Call the named function in the device-specific module, passing
1514 the given args and kwargs. The first argument to the call will be
1515 the DeviceSpecific object itself. If there is no module, or the
1516 module does not define the function, return the value of the
1517 'default' kwarg (which itself defaults to None)."""
1518 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001519 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001520 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1521
1522 def FullOTA_Assertions(self):
1523 """Called after emitting the block of assertions at the top of a
1524 full OTA package. Implementations can add whatever additional
1525 assertions they like."""
1526 return self._DoCall("FullOTA_Assertions")
1527
Doug Zongkere5ff5902012-01-17 10:55:37 -08001528 def FullOTA_InstallBegin(self):
1529 """Called at the start of full OTA installation."""
1530 return self._DoCall("FullOTA_InstallBegin")
1531
Yifan Hong10c530d2018-12-27 17:34:18 -08001532 def FullOTA_GetBlockDifferences(self):
1533 """Called during full OTA installation and verification.
1534 Implementation should return a list of BlockDifference objects describing
1535 the update on each additional partitions.
1536 """
1537 return self._DoCall("FullOTA_GetBlockDifferences")
1538
Doug Zongker05d3dea2009-06-22 11:32:31 -07001539 def FullOTA_InstallEnd(self):
1540 """Called at the end of full OTA installation; typically this is
1541 used to install the image for the device's baseband processor."""
1542 return self._DoCall("FullOTA_InstallEnd")
1543
1544 def IncrementalOTA_Assertions(self):
1545 """Called after emitting the block of assertions at the top of an
1546 incremental OTA package. Implementations can add whatever
1547 additional assertions they like."""
1548 return self._DoCall("IncrementalOTA_Assertions")
1549
Doug Zongkere5ff5902012-01-17 10:55:37 -08001550 def IncrementalOTA_VerifyBegin(self):
1551 """Called at the start of the verification phase of incremental
1552 OTA installation; additional checks can be placed here to abort
1553 the script before any changes are made."""
1554 return self._DoCall("IncrementalOTA_VerifyBegin")
1555
Doug Zongker05d3dea2009-06-22 11:32:31 -07001556 def IncrementalOTA_VerifyEnd(self):
1557 """Called at the end of the verification phase of incremental OTA
1558 installation; additional checks can be placed here to abort the
1559 script before any changes are made."""
1560 return self._DoCall("IncrementalOTA_VerifyEnd")
1561
Doug Zongkere5ff5902012-01-17 10:55:37 -08001562 def IncrementalOTA_InstallBegin(self):
1563 """Called at the start of incremental OTA installation (after
1564 verification is complete)."""
1565 return self._DoCall("IncrementalOTA_InstallBegin")
1566
Yifan Hong10c530d2018-12-27 17:34:18 -08001567 def IncrementalOTA_GetBlockDifferences(self):
1568 """Called during incremental OTA installation and verification.
1569 Implementation should return a list of BlockDifference objects describing
1570 the update on each additional partitions.
1571 """
1572 return self._DoCall("IncrementalOTA_GetBlockDifferences")
1573
Doug Zongker05d3dea2009-06-22 11:32:31 -07001574 def IncrementalOTA_InstallEnd(self):
1575 """Called at the end of incremental OTA installation; typically
1576 this is used to install the image for the device's baseband
1577 processor."""
1578 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001579
Tao Bao9bc6bb22015-11-09 16:58:28 -08001580 def VerifyOTA_Assertions(self):
1581 return self._DoCall("VerifyOTA_Assertions")
1582
Tao Bao76def242017-11-21 09:25:31 -08001583
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001584class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001585 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001586 self.name = name
1587 self.data = data
1588 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001589 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001590 self.sha1 = sha1(data).hexdigest()
1591
1592 @classmethod
1593 def FromLocalFile(cls, name, diskname):
1594 f = open(diskname, "rb")
1595 data = f.read()
1596 f.close()
1597 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001598
1599 def WriteToTemp(self):
1600 t = tempfile.NamedTemporaryFile()
1601 t.write(self.data)
1602 t.flush()
1603 return t
1604
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001605 def WriteToDir(self, d):
1606 with open(os.path.join(d, self.name), "wb") as fp:
1607 fp.write(self.data)
1608
Geremy Condra36bd3652014-02-06 19:45:10 -08001609 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001610 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001611
Tao Bao76def242017-11-21 09:25:31 -08001612
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001613DIFF_PROGRAM_BY_EXT = {
1614 ".gz" : "imgdiff",
1615 ".zip" : ["imgdiff", "-z"],
1616 ".jar" : ["imgdiff", "-z"],
1617 ".apk" : ["imgdiff", "-z"],
1618 ".img" : "imgdiff",
1619 }
1620
Tao Bao76def242017-11-21 09:25:31 -08001621
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001622class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001623 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001624 self.tf = tf
1625 self.sf = sf
1626 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001627 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001628
1629 def ComputePatch(self):
1630 """Compute the patch (as a string of data) needed to turn sf into
1631 tf. Returns the same tuple as GetPatch()."""
1632
1633 tf = self.tf
1634 sf = self.sf
1635
Doug Zongker24cd2802012-08-14 16:36:15 -07001636 if self.diff_program:
1637 diff_program = self.diff_program
1638 else:
1639 ext = os.path.splitext(tf.name)[1]
1640 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001641
1642 ttemp = tf.WriteToTemp()
1643 stemp = sf.WriteToTemp()
1644
1645 ext = os.path.splitext(tf.name)[1]
1646
1647 try:
1648 ptemp = tempfile.NamedTemporaryFile()
1649 if isinstance(diff_program, list):
1650 cmd = copy.copy(diff_program)
1651 else:
1652 cmd = [diff_program]
1653 cmd.append(stemp.name)
1654 cmd.append(ttemp.name)
1655 cmd.append(ptemp.name)
1656 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001657 err = []
1658 def run():
1659 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001660 if e:
1661 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001662 th = threading.Thread(target=run)
1663 th.start()
1664 th.join(timeout=300) # 5 mins
1665 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001666 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001667 p.terminate()
1668 th.join(5)
1669 if th.is_alive():
1670 p.kill()
1671 th.join()
1672
Tianjie Xua2a9f992018-01-05 15:15:54 -08001673 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001674 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001675 self.patch = None
1676 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001677 diff = ptemp.read()
1678 finally:
1679 ptemp.close()
1680 stemp.close()
1681 ttemp.close()
1682
1683 self.patch = diff
1684 return self.tf, self.sf, self.patch
1685
1686
1687 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001688 """Returns a tuple of (target_file, source_file, patch_data).
1689
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001690 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001691 computing the patch failed.
1692 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001693 return self.tf, self.sf, self.patch
1694
1695
1696def ComputeDifferences(diffs):
1697 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001698 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001699
1700 # Do the largest files first, to try and reduce the long-pole effect.
1701 by_size = [(i.tf.size, i) for i in diffs]
1702 by_size.sort(reverse=True)
1703 by_size = [i[1] for i in by_size]
1704
1705 lock = threading.Lock()
1706 diff_iter = iter(by_size) # accessed under lock
1707
1708 def worker():
1709 try:
1710 lock.acquire()
1711 for d in diff_iter:
1712 lock.release()
1713 start = time.time()
1714 d.ComputePatch()
1715 dur = time.time() - start
1716 lock.acquire()
1717
1718 tf, sf, patch = d.GetPatch()
1719 if sf.name == tf.name:
1720 name = tf.name
1721 else:
1722 name = "%s (%s)" % (tf.name, sf.name)
1723 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001724 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001725 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001726 logger.info(
1727 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1728 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001729 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001730 except Exception:
1731 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001732 raise
1733
1734 # start worker threads; wait for them all to finish.
1735 threads = [threading.Thread(target=worker)
1736 for i in range(OPTIONS.worker_threads)]
1737 for th in threads:
1738 th.start()
1739 while threads:
1740 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001741
1742
Dan Albert8b72aef2015-03-23 19:13:21 -07001743class BlockDifference(object):
1744 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001745 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001746 self.tgt = tgt
1747 self.src = src
1748 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001749 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001750 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001751
Tao Baodd2a5892015-03-12 12:32:37 -07001752 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001753 version = max(
1754 int(i) for i in
1755 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001756 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001757 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001758
1759 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001760 version=self.version,
1761 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001762 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001763 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001764 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001765 self.touched_src_ranges = b.touched_src_ranges
1766 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001767
Yifan Hong10c530d2018-12-27 17:34:18 -08001768 # On devices with dynamic partitions, for new partitions,
1769 # src is None but OPTIONS.source_info_dict is not.
1770 if OPTIONS.source_info_dict is None:
1771 is_dynamic_build = OPTIONS.info_dict.get(
1772 "use_dynamic_partitions") == "true"
Tao Baoaac4ad52015-10-16 15:26:34 -07001773 else:
Yifan Hong10c530d2018-12-27 17:34:18 -08001774 is_dynamic_build = OPTIONS.source_info_dict.get(
1775 "use_dynamic_partitions") == "true"
1776
1777 # For dynamic partitions builds, always check partition list in target build
1778 # because new partitions may be added.
1779 is_dynamic = is_dynamic_build and partition in shlex.split(
1780 OPTIONS.info_dict.get("dynamic_partition_list", "").strip())
1781
1782 if is_dynamic:
1783 self.device = 'map_partition("%s")' % partition
1784 else:
1785 if OPTIONS.source_info_dict is None:
1786 _, device_path = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1787 else:
1788 _, device_path = GetTypeAndDevice("/" + partition,
1789 OPTIONS.source_info_dict)
1790 self.device = '"%s"' % device_path
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001791
Tao Baod8d14be2016-02-04 14:26:02 -08001792 @property
1793 def required_cache(self):
1794 return self._required_cache
1795
Tao Bao76def242017-11-21 09:25:31 -08001796 def WriteScript(self, script, output_zip, progress=None,
1797 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001798 if not self.src:
1799 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001800 script.Print("Patching %s image unconditionally..." % (self.partition,))
1801 else:
1802 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001803
Dan Albert8b72aef2015-03-23 19:13:21 -07001804 if progress:
1805 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001806 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001807
1808 if write_verify_script:
Yifan Hong10c530d2018-12-27 17:34:18 -08001809 self.WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001810
Tao Bao9bc6bb22015-11-09 16:58:28 -08001811 def WriteStrictVerifyScript(self, script):
1812 """Verify all the blocks in the care_map, including clobbered blocks.
1813
1814 This differs from the WriteVerifyScript() function: a) it prints different
1815 error messages; b) it doesn't allow half-way updated images to pass the
1816 verification."""
1817
1818 partition = self.partition
1819 script.Print("Verifying %s..." % (partition,))
1820 ranges = self.tgt.care_map
1821 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001822 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001823 'range_sha1(%s, "%s") == "%s" && ui_print(" Verified.") || '
1824 'ui_print("%s has unexpected contents.");' % (
Tao Bao76def242017-11-21 09:25:31 -08001825 self.device, ranges_str,
1826 self.tgt.TotalSha1(include_clobbered_blocks=True),
Yifan Hong10c530d2018-12-27 17:34:18 -08001827 self.partition))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001828 script.AppendExtra("")
1829
Tao Baod522bdc2016-04-12 15:53:16 -07001830 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001831 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001832
1833 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001834 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001835 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001836
1837 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001838 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001839 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001840 ranges = self.touched_src_ranges
1841 expected_sha1 = self.touched_src_sha1
1842 else:
1843 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1844 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001845
1846 # No blocks to be checked, skipping.
1847 if not ranges:
1848 return
1849
Tao Bao5ece99d2015-05-12 11:42:31 -07001850 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001851 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001852 'if (range_sha1(%s, "%s") == "%s" || block_image_verify(%s, '
Tao Bao76def242017-11-21 09:25:31 -08001853 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1854 '"%s.patch.dat")) then' % (
1855 self.device, ranges_str, expected_sha1,
1856 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001857 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001858 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001859
Tianjie Xufc3422a2015-12-15 11:53:59 -08001860 if self.version >= 4:
1861
1862 # Bug: 21124327
1863 # When generating incrementals for the system and vendor partitions in
1864 # version 4 or newer, explicitly check the first block (which contains
1865 # the superblock) of the partition to see if it's what we expect. If
1866 # this check fails, give an explicit log message about the partition
1867 # having been remounted R/W (the most likely explanation).
1868 if self.check_first_block:
Yifan Hong10c530d2018-12-27 17:34:18 -08001869 script.AppendExtra('check_first_block(%s);' % (self.device,))
Tianjie Xufc3422a2015-12-15 11:53:59 -08001870
1871 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001872 if partition == "system":
1873 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1874 else:
1875 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001876 script.AppendExtra((
Yifan Hong10c530d2018-12-27 17:34:18 -08001877 'ifelse (block_image_recover({device}, "{ranges}") && '
1878 'block_image_verify({device}, '
Tianjie Xufc3422a2015-12-15 11:53:59 -08001879 'package_extract_file("{partition}.transfer.list"), '
1880 '"{partition}.new.dat", "{partition}.patch.dat"), '
1881 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001882 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001883 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001884 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001885
Tao Baodd2a5892015-03-12 12:32:37 -07001886 # Abort the OTA update. Note that the incremental OTA cannot be applied
1887 # even if it may match the checksum of the target partition.
1888 # a) If version < 3, operations like move and erase will make changes
1889 # unconditionally and damage the partition.
1890 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001891 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001892 if partition == "system":
1893 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1894 else:
1895 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1896 script.AppendExtra((
1897 'abort("E%d: %s partition has unexpected contents");\n'
1898 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001899
Yifan Hong10c530d2018-12-27 17:34:18 -08001900 def WritePostInstallVerifyScript(self, script):
Tao Bao5fcaaef2015-06-01 13:40:49 -07001901 partition = self.partition
1902 script.Print('Verifying the updated %s image...' % (partition,))
1903 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1904 ranges = self.tgt.care_map
1905 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001906 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001907 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001908 self.device, ranges_str,
1909 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001910
1911 # Bug: 20881595
1912 # Verify that extended blocks are really zeroed out.
1913 if self.tgt.extended:
1914 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001915 script.AppendExtra(
Yifan Hong10c530d2018-12-27 17:34:18 -08001916 'if range_sha1(%s, "%s") == "%s" then' % (
Tao Bao76def242017-11-21 09:25:31 -08001917 self.device, ranges_str,
1918 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001919 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001920 if partition == "system":
1921 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1922 else:
1923 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001924 script.AppendExtra(
1925 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001926 ' abort("E%d: %s partition has unexpected non-zero contents after '
1927 'OTA update");\n'
1928 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001929 else:
1930 script.Print('Verified the updated %s image.' % (partition,))
1931
Tianjie Xu209db462016-05-24 17:34:52 -07001932 if partition == "system":
1933 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1934 else:
1935 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1936
Tao Bao5fcaaef2015-06-01 13:40:49 -07001937 script.AppendExtra(
1938 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001939 ' abort("E%d: %s partition has unexpected contents after OTA '
1940 'update");\n'
1941 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001942
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001943 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001944 ZipWrite(output_zip,
1945 '{}.transfer.list'.format(self.path),
1946 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001947
Tao Bao76def242017-11-21 09:25:31 -08001948 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1949 # its size. Quailty 9 almost triples the compression time but doesn't
1950 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001951 # zip | brotli(quality 6) | brotli(quality 9)
1952 # compressed_size: 942M | 869M (~8% reduced) | 854M
1953 # compression_time: 75s | 265s | 719s
1954 # decompression_time: 15s | 25s | 25s
1955
1956 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001957 brotli_cmd = ['brotli', '--quality=6',
1958 '--output={}.new.dat.br'.format(self.path),
1959 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001960 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07001961 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001962
1963 new_data_name = '{}.new.dat.br'.format(self.partition)
1964 ZipWrite(output_zip,
1965 '{}.new.dat.br'.format(self.path),
1966 new_data_name,
1967 compress_type=zipfile.ZIP_STORED)
1968 else:
1969 new_data_name = '{}.new.dat'.format(self.partition)
1970 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1971
Dan Albert8e0178d2015-01-27 15:53:15 -08001972 ZipWrite(output_zip,
1973 '{}.patch.dat'.format(self.path),
1974 '{}.patch.dat'.format(self.partition),
1975 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001976
Tianjie Xu209db462016-05-24 17:34:52 -07001977 if self.partition == "system":
1978 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1979 else:
1980 code = ErrorCode.VENDOR_UPDATE_FAILURE
1981
Yifan Hong10c530d2018-12-27 17:34:18 -08001982 call = ('block_image_update({device}, '
Dan Albert8e0178d2015-01-27 15:53:15 -08001983 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001984 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001985 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001986 device=self.device, partition=self.partition,
1987 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001988 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001989
Dan Albert8b72aef2015-03-23 19:13:21 -07001990 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001991 data = source.ReadRangeSet(ranges)
1992 ctx = sha1()
1993
1994 for p in data:
1995 ctx.update(p)
1996
1997 return ctx.hexdigest()
1998
Tao Baoe9b61912015-07-09 17:37:49 -07001999 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
2000 """Return the hash value for all zero blocks."""
2001 zero_block = '\x00' * 4096
2002 ctx = sha1()
2003 for _ in range(num_blocks):
2004 ctx.update(zero_block)
2005
2006 return ctx.hexdigest()
2007
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07002008
2009DataImage = blockimgdiff.DataImage
2010
Tao Bao76def242017-11-21 09:25:31 -08002011
Doug Zongker96a57e72010-09-26 14:57:41 -07002012# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07002013PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07002014 "ext4": "EMMC",
2015 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07002016 "f2fs": "EMMC",
2017 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07002018}
Doug Zongker96a57e72010-09-26 14:57:41 -07002019
Tao Bao76def242017-11-21 09:25:31 -08002020
Doug Zongker96a57e72010-09-26 14:57:41 -07002021def GetTypeAndDevice(mount_point, info):
2022 fstab = info["fstab"]
2023 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07002024 return (PARTITION_TYPES[fstab[mount_point].fs_type],
2025 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07002026 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07002027 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002028
2029
2030def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08002031 """Parses and converts a PEM-encoded certificate into DER-encoded.
2032
2033 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2034
2035 Returns:
2036 The decoded certificate string.
2037 """
2038 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002039 save = False
2040 for line in data.split("\n"):
2041 if "--END CERTIFICATE--" in line:
2042 break
2043 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002044 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002045 if "--BEGIN CERTIFICATE--" in line:
2046 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002047 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002048 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002049
Tao Bao04e1f012018-02-04 12:13:35 -08002050
2051def ExtractPublicKey(cert):
2052 """Extracts the public key (PEM-encoded) from the given certificate file.
2053
2054 Args:
2055 cert: The certificate filename.
2056
2057 Returns:
2058 The public key string.
2059
2060 Raises:
2061 AssertionError: On non-zero return from 'openssl'.
2062 """
2063 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2064 # While openssl 1.1 writes the key into the given filename followed by '-out',
2065 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2066 # stdout instead.
2067 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2068 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2069 pubkey, stderrdata = proc.communicate()
2070 assert proc.returncode == 0, \
2071 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2072 return pubkey
2073
2074
Doug Zongker412c02f2014-02-13 10:58:24 -08002075def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2076 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002077 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002078
Tao Bao6d5d6232018-03-09 17:04:42 -08002079 Most of the space in the boot and recovery images is just the kernel, which is
2080 identical for the two, so the resulting patch should be efficient. Add it to
2081 the output zip, along with a shell script that is run from init.rc on first
2082 boot to actually do the patching and install the new recovery image.
2083
2084 Args:
2085 input_dir: The top-level input directory of the target-files.zip.
2086 output_sink: The callback function that writes the result.
2087 recovery_img: File object for the recovery image.
2088 boot_img: File objects for the boot image.
2089 info_dict: A dict returned by common.LoadInfoDict() on the input
2090 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002091 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002092 if info_dict is None:
2093 info_dict = OPTIONS.info_dict
2094
Tao Bao6d5d6232018-03-09 17:04:42 -08002095 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002096
Tao Baof2cffbd2015-07-22 12:33:18 -07002097 if full_recovery_image:
2098 output_sink("etc/recovery.img", recovery_img.data)
2099
2100 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002101 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002102 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002103 # With system-root-image, boot and recovery images will have mismatching
2104 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2105 # to handle such a case.
2106 if system_root_image:
2107 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002108 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002109 assert not os.path.exists(path)
2110 else:
2111 diff_program = ["imgdiff"]
2112 if os.path.exists(path):
2113 diff_program.append("-b")
2114 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002115 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002116 else:
2117 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002118
2119 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2120 _, _, patch = d.ComputePatch()
2121 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002122
Dan Albertebb19aa2015-03-27 19:11:53 -07002123 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002124 # The following GetTypeAndDevice()s need to use the path in the target
2125 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002126 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2127 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2128 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002129 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002130
Tao Baof2cffbd2015-07-22 12:33:18 -07002131 if full_recovery_image:
2132 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002133if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2134 applypatch \\
2135 --flash /system/etc/recovery.img \\
2136 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2137 log -t recovery "Installing new recovery image: succeeded" || \\
2138 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002139else
2140 log -t recovery "Recovery image already installed"
2141fi
2142""" % {'type': recovery_type,
2143 'device': recovery_device,
2144 'sha1': recovery_img.sha1,
2145 'size': recovery_img.size}
2146 else:
2147 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002148if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2149 applypatch %(bonus_args)s \\
2150 --patch /system/recovery-from-boot.p \\
2151 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2152 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2153 log -t recovery "Installing new recovery image: succeeded" || \\
2154 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002155else
2156 log -t recovery "Recovery image already installed"
2157fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002158""" % {'boot_size': boot_img.size,
2159 'boot_sha1': boot_img.sha1,
2160 'recovery_size': recovery_img.size,
2161 'recovery_sha1': recovery_img.sha1,
2162 'boot_type': boot_type,
2163 'boot_device': boot_device,
2164 'recovery_type': recovery_type,
2165 'recovery_device': recovery_device,
2166 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002167
2168 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002169 # in the L release.
2170 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002171
Tao Bao32fcdab2018-10-12 10:30:39 -07002172 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002173
2174 output_sink(sh_location, sh)
Yifan Hong10c530d2018-12-27 17:34:18 -08002175
2176
2177class DynamicPartitionUpdate(object):
2178 def __init__(self, src_group=None, tgt_group=None, progress=None,
2179 block_difference=None):
2180 self.src_group = src_group
2181 self.tgt_group = tgt_group
2182 self.progress = progress
2183 self.block_difference = block_difference
2184
2185 @property
2186 def src_size(self):
2187 if not self.block_difference:
2188 return 0
2189 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.src)
2190
2191 @property
2192 def tgt_size(self):
2193 if not self.block_difference:
2194 return 0
2195 return DynamicPartitionUpdate._GetSparseImageSize(self.block_difference.tgt)
2196
2197 @staticmethod
2198 def _GetSparseImageSize(img):
2199 if not img:
2200 return 0
2201 return img.blocksize * img.total_blocks
2202
2203
2204class DynamicGroupUpdate(object):
2205 def __init__(self, src_size=None, tgt_size=None):
2206 # None: group does not exist. 0: no size limits.
2207 self.src_size = src_size
2208 self.tgt_size = tgt_size
2209
2210
2211class DynamicPartitionsDifference(object):
2212 def __init__(self, info_dict, block_diffs, progress_dict=None,
2213 source_info_dict=None):
2214 if progress_dict is None:
2215 progress_dict = dict()
2216
2217 self._remove_all_before_apply = False
2218 if source_info_dict is None:
2219 self._remove_all_before_apply = True
2220 source_info_dict = dict()
2221
2222 block_diff_dict = {e.partition:e for e in block_diffs}
2223 assert len(block_diff_dict) == len(block_diffs), \
2224 "Duplicated BlockDifference object for {}".format(
2225 [partition for partition, count in
2226 collections.Counter(e.partition for e in block_diffs).items()
2227 if count > 1])
2228
Yifan Hong79997e52019-01-23 16:56:19 -08002229 self._partition_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002230
2231 for p, block_diff in block_diff_dict.items():
2232 self._partition_updates[p] = DynamicPartitionUpdate()
2233 self._partition_updates[p].block_difference = block_diff
2234
2235 for p, progress in progress_dict.items():
2236 if p in self._partition_updates:
2237 self._partition_updates[p].progress = progress
2238
2239 tgt_groups = shlex.split(info_dict.get(
2240 "super_partition_groups", "").strip())
2241 src_groups = shlex.split(source_info_dict.get(
2242 "super_partition_groups", "").strip())
2243
2244 for g in tgt_groups:
2245 for p in shlex.split(info_dict.get(
2246 "super_%s_partition_list" % g, "").strip()):
2247 assert p in self._partition_updates, \
2248 "{} is in target super_{}_partition_list but no BlockDifference " \
2249 "object is provided.".format(p, g)
2250 self._partition_updates[p].tgt_group = g
2251
2252 for g in src_groups:
2253 for p in shlex.split(source_info_dict.get(
2254 "super_%s_partition_list" % g, "").strip()):
2255 assert p in self._partition_updates, \
2256 "{} is in source super_{}_partition_list but no BlockDifference " \
2257 "object is provided.".format(p, g)
2258 self._partition_updates[p].src_group = g
2259
Yifan Hong45433e42019-01-18 13:55:25 -08002260 target_dynamic_partitions = set(shlex.split(info_dict.get(
2261 "dynamic_partition_list", "").strip()))
2262 block_diffs_with_target = set(p for p, u in self._partition_updates.items()
2263 if u.tgt_size)
2264 assert block_diffs_with_target == target_dynamic_partitions, \
2265 "Target Dynamic partitions: {}, BlockDifference with target: {}".format(
2266 list(target_dynamic_partitions), list(block_diffs_with_target))
2267
2268 source_dynamic_partitions = set(shlex.split(source_info_dict.get(
2269 "dynamic_partition_list", "").strip()))
2270 block_diffs_with_source = set(p for p, u in self._partition_updates.items()
2271 if u.src_size)
2272 assert block_diffs_with_source == source_dynamic_partitions, \
2273 "Source Dynamic partitions: {}, BlockDifference with source: {}".format(
2274 list(source_dynamic_partitions), list(block_diffs_with_source))
2275
Yifan Hong10c530d2018-12-27 17:34:18 -08002276 if self._partition_updates:
2277 logger.info("Updating dynamic partitions %s",
2278 self._partition_updates.keys())
2279
Yifan Hong79997e52019-01-23 16:56:19 -08002280 self._group_updates = collections.OrderedDict()
Yifan Hong10c530d2018-12-27 17:34:18 -08002281
2282 for g in tgt_groups:
2283 self._group_updates[g] = DynamicGroupUpdate()
2284 self._group_updates[g].tgt_size = int(info_dict.get(
2285 "super_%s_group_size" % g, "0").strip())
2286
2287 for g in src_groups:
2288 if g not in self._group_updates:
2289 self._group_updates[g] = DynamicGroupUpdate()
2290 self._group_updates[g].src_size = int(source_info_dict.get(
2291 "super_%s_group_size" % g, "0").strip())
2292
2293 self._Compute()
2294
2295 def WriteScript(self, script, output_zip, write_verify_script=False):
2296 script.Comment('--- Start patching dynamic partitions ---')
2297 for p, u in self._partition_updates.items():
2298 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2299 script.Comment('Patch partition %s' % p)
2300 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2301 write_verify_script=False)
2302
2303 op_list_path = MakeTempFile()
2304 with open(op_list_path, 'w') as f:
2305 for line in self._op_list:
2306 f.write('{}\n'.format(line))
2307
2308 ZipWrite(output_zip, op_list_path, "dynamic_partitions_op_list")
2309
2310 script.Comment('Update dynamic partition metadata')
2311 script.AppendExtra('assert(update_dynamic_partitions('
2312 'package_extract_file("dynamic_partitions_op_list")));')
2313
2314 if write_verify_script:
2315 for p, u in self._partition_updates.items():
2316 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2317 u.block_difference.WritePostInstallVerifyScript(script)
2318 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2319
2320 for p, u in self._partition_updates.items():
2321 if u.tgt_size and u.src_size <= u.tgt_size:
2322 script.Comment('Patch partition %s' % p)
2323 u.block_difference.WriteScript(script, output_zip, progress=u.progress,
2324 write_verify_script=write_verify_script)
2325 if write_verify_script:
2326 script.AppendExtra('unmap_partition("%s");' % p) # ignore errors
2327
2328 script.Comment('--- End patching dynamic partitions ---')
2329
2330 def _Compute(self):
2331 self._op_list = list()
2332
2333 def append(line):
2334 self._op_list.append(line)
2335
2336 def comment(line):
2337 self._op_list.append("# %s" % line)
2338
2339 if self._remove_all_before_apply:
2340 comment('Remove all existing dynamic partitions and groups before '
2341 'applying full OTA')
2342 append('remove_all_groups')
2343
2344 for p, u in self._partition_updates.items():
2345 if u.src_group and not u.tgt_group:
2346 append('remove %s' % p)
2347
2348 for p, u in self._partition_updates.items():
2349 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2350 comment('Move partition %s from %s to default' % (p, u.src_group))
2351 append('move %s default' % p)
2352
2353 for p, u in self._partition_updates.items():
2354 if u.src_size and u.tgt_size and u.src_size > u.tgt_size:
2355 comment('Shrink partition %s from %d to %d' %
2356 (p, u.src_size, u.tgt_size))
2357 append('resize %s %s' % (p, u.tgt_size))
2358
2359 for g, u in self._group_updates.items():
2360 if u.src_size is not None and u.tgt_size is None:
2361 append('remove_group %s' % g)
2362 if (u.src_size is not None and u.tgt_size is not None and
2363 u.src_size > u.tgt_size):
2364 comment('Shrink group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2365 append('resize_group %s %d' % (g, u.tgt_size))
2366
2367 for g, u in self._group_updates.items():
2368 if u.src_size is None and u.tgt_size is not None:
2369 comment('Add group %s with maximum size %d' % (g, u.tgt_size))
2370 append('add_group %s %d' % (g, u.tgt_size))
2371 if (u.src_size is not None and u.tgt_size is not None and
2372 u.src_size < u.tgt_size):
2373 comment('Grow group %s from %d to %d' % (g, u.src_size, u.tgt_size))
2374 append('resize_group %s %d' % (g, u.tgt_size))
2375
2376 for p, u in self._partition_updates.items():
2377 if u.tgt_group and not u.src_group:
2378 comment('Add partition %s to group %s' % (p, u.tgt_group))
2379 append('add %s %s' % (p, u.tgt_group))
2380
2381 for p, u in self._partition_updates.items():
2382 if u.tgt_size and u.src_size < u.tgt_size:
2383 comment('Grow partition %s from %d to %d' % (p, u.src_size, u.tgt_size))
2384 append('resize %s %d' % (p, u.tgt_size))
2385
2386 for p, u in self._partition_updates.items():
2387 if u.src_group and u.tgt_group and u.src_group != u.tgt_group:
2388 comment('Move partition %s from default to %s' %
2389 (p, u.tgt_group))
2390 append('move %s %s' % (p, u.tgt_group))