blob: 59a12f170a361aedd4ad583ff81e351e84b212ed [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
Doug Zongkerea5d7a92010-09-12 15:26:16 -070017import copy
Doug Zongker8ce7c252009-05-22 13:34:54 -070018import errno
Doug Zongkereef39442009-04-02 12:14:19 -070019import getopt
20import getpass
Narayan Kamatha07bf042017-08-14 14:49:21 +010021import gzip
Doug Zongker05d3dea2009-06-22 11:32:31 -070022import imp
Tao Bao32fcdab2018-10-12 10:30:39 -070023import json
24import logging
25import logging.config
Doug Zongkereef39442009-04-02 12:14:19 -070026import os
Ying Wang7e6d4e42010-12-13 16:25:36 -080027import platform
Doug Zongkereef39442009-04-02 12:14:19 -070028import re
T.R. Fullhart37e10522013-03-18 10:31:26 -070029import shlex
Doug Zongkereef39442009-04-02 12:14:19 -070030import shutil
Tao Baoc765cca2018-01-31 17:32:40 -080031import string
Doug Zongkereef39442009-04-02 12:14:19 -070032import subprocess
33import sys
34import tempfile
Doug Zongkerea5d7a92010-09-12 15:26:16 -070035import threading
36import time
Doug Zongker048e7ca2009-06-15 14:31:53 -070037import zipfile
Tao Bao12d87fc2018-01-31 12:18:52 -080038from hashlib import sha1, sha256
Doug Zongkereef39442009-04-02 12:14:19 -070039
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070040import blockimgdiff
Tao Baoc765cca2018-01-31 17:32:40 -080041import sparse_img
Doug Zongkerab7ca1d2014-08-26 10:40:28 -070042
Tao Bao32fcdab2018-10-12 10:30:39 -070043logger = logging.getLogger(__name__)
44
Tao Bao986ee862018-10-04 15:46:16 -070045
Dan Albert8b72aef2015-03-23 19:13:21 -070046class Options(object):
47 def __init__(self):
48 platform_search_path = {
49 "linux2": "out/host/linux-x86",
50 "darwin": "out/host/darwin-x86",
Doug Zongker85448772014-09-09 14:59:20 -070051 }
Doug Zongker85448772014-09-09 14:59:20 -070052
Tao Bao76def242017-11-21 09:25:31 -080053 self.search_path = platform_search_path.get(sys.platform)
Dan Albert8b72aef2015-03-23 19:13:21 -070054 self.signapk_path = "framework/signapk.jar" # Relative to search_path
Alex Klyubin9667b182015-12-10 13:38:50 -080055 self.signapk_shared_library_path = "lib64" # Relative to search_path
Dan Albert8b72aef2015-03-23 19:13:21 -070056 self.extra_signapk_args = []
57 self.java_path = "java" # Use the one on the path by default.
Tao Baoe95540e2016-11-08 12:08:53 -080058 self.java_args = ["-Xmx2048m"] # The default JVM args.
Dan Albert8b72aef2015-03-23 19:13:21 -070059 self.public_key_suffix = ".x509.pem"
60 self.private_key_suffix = ".pk8"
Dan Albertcd9ecc02015-03-27 16:37:23 -070061 # use otatools built boot_signer by default
62 self.boot_signer_path = "boot_signer"
Baligh Uddin601ddea2015-06-09 15:48:14 -070063 self.boot_signer_args = []
64 self.verity_signer_path = None
65 self.verity_signer_args = []
Dan Albert8b72aef2015-03-23 19:13:21 -070066 self.verbose = False
67 self.tempfiles = []
68 self.device_specific = None
69 self.extras = {}
70 self.info_dict = None
Tao Bao6f0b2192015-10-13 16:37:12 -070071 self.source_info_dict = None
72 self.target_info_dict = None
Dan Albert8b72aef2015-03-23 19:13:21 -070073 self.worker_threads = None
Tao Bao575d68a2015-08-07 19:49:45 -070074 # Stash size cannot exceed cache_size * threshold.
75 self.cache_size = None
76 self.stash_threshold = 0.8
Dan Albert8b72aef2015-03-23 19:13:21 -070077
78
79OPTIONS = Options()
Doug Zongkereef39442009-04-02 12:14:19 -070080
Tao Bao71197512018-10-11 14:08:45 -070081# The block size that's used across the releasetools scripts.
82BLOCK_SIZE = 4096
83
Doug Zongkerf6a53aa2009-12-15 15:06:55 -080084# Values for "certificate" in apkcerts that mean special things.
85SPECIAL_CERT_STRINGS = ("PRESIGNED", "EXTERNAL")
86
Tao Bao9dd909e2017-11-14 11:27:32 -080087# The partitions allowed to be signed by AVB (Android verified boot 2.0).
Dario Freni5f681e12018-05-29 13:09:01 +010088AVB_PARTITIONS = ('boot', 'recovery', 'system', 'vendor', 'product',
Dario Freni924af7d2018-08-17 00:56:14 +010089 'product_services', 'dtbo', 'odm')
Tao Bao9dd909e2017-11-14 11:27:32 -080090
Tianjie Xu861f4132018-09-12 11:49:33 -070091# Partitions that should have their care_map added to META/care_map.pb
92PARTITIONS_WITH_CARE_MAP = ('system', 'vendor', 'product', 'product_services',
93 'odm')
94
95
Tianjie Xu209db462016-05-24 17:34:52 -070096class ErrorCode(object):
97 """Define error_codes for failures that happen during the actual
98 update package installation.
99
100 Error codes 0-999 are reserved for failures before the package
101 installation (i.e. low battery, package verification failure).
102 Detailed code in 'bootable/recovery/error_code.h' """
103
104 SYSTEM_VERIFICATION_FAILURE = 1000
105 SYSTEM_UPDATE_FAILURE = 1001
106 SYSTEM_UNEXPECTED_CONTENTS = 1002
107 SYSTEM_NONZERO_CONTENTS = 1003
108 SYSTEM_RECOVER_FAILURE = 1004
109 VENDOR_VERIFICATION_FAILURE = 2000
110 VENDOR_UPDATE_FAILURE = 2001
111 VENDOR_UNEXPECTED_CONTENTS = 2002
112 VENDOR_NONZERO_CONTENTS = 2003
113 VENDOR_RECOVER_FAILURE = 2004
114 OEM_PROP_MISMATCH = 3000
115 FINGERPRINT_MISMATCH = 3001
116 THUMBPRINT_MISMATCH = 3002
117 OLDER_BUILD = 3003
118 DEVICE_MISMATCH = 3004
119 BAD_PATCH_FILE = 3005
120 INSUFFICIENT_CACHE_SPACE = 3006
121 TUNE_PARTITION_FAILURE = 3007
122 APPLY_PATCH_FAILURE = 3008
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800123
Tao Bao80921982018-03-21 21:02:19 -0700124
Dan Albert8b72aef2015-03-23 19:13:21 -0700125class ExternalError(RuntimeError):
126 pass
Doug Zongkereef39442009-04-02 12:14:19 -0700127
128
Tao Bao32fcdab2018-10-12 10:30:39 -0700129def InitLogging():
130 DEFAULT_LOGGING_CONFIG = {
131 'version': 1,
132 'disable_existing_loggers': False,
133 'formatters': {
134 'standard': {
135 'format':
136 '%(asctime)s - %(filename)s - %(levelname)-8s: %(message)s',
137 'datefmt': '%Y-%m-%d %H:%M:%S',
138 },
139 },
140 'handlers': {
141 'default': {
142 'class': 'logging.StreamHandler',
143 'formatter': 'standard',
144 },
145 },
146 'loggers': {
147 '': {
148 'handlers': ['default'],
149 'level': 'WARNING',
150 'propagate': True,
151 }
152 }
153 }
154 env_config = os.getenv('LOGGING_CONFIG')
155 if env_config:
156 with open(env_config) as f:
157 config = json.load(f)
158 else:
159 config = DEFAULT_LOGGING_CONFIG
160
161 # Increase the logging level for verbose mode.
162 if OPTIONS.verbose:
163 config = copy.deepcopy(DEFAULT_LOGGING_CONFIG)
164 config['loggers']['']['level'] = 'INFO'
165
166 logging.config.dictConfig(config)
167
168
Tao Bao39451582017-05-04 11:10:47 -0700169def Run(args, verbose=None, **kwargs):
Tao Bao73dd4f42018-10-04 16:25:33 -0700170 """Creates and returns a subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700171
Tao Bao73dd4f42018-10-04 16:25:33 -0700172 Args:
173 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700174 verbose: Whether the commands should be shown. Default to the global
175 verbosity if unspecified.
Tao Bao73dd4f42018-10-04 16:25:33 -0700176 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
177 stdin, etc. stdout and stderr will default to subprocess.PIPE and
178 subprocess.STDOUT respectively unless caller specifies any of them.
179
180 Returns:
181 A subprocess.Popen object.
Tao Bao39451582017-05-04 11:10:47 -0700182 """
Tao Bao73dd4f42018-10-04 16:25:33 -0700183 if 'stdout' not in kwargs and 'stderr' not in kwargs:
184 kwargs['stdout'] = subprocess.PIPE
185 kwargs['stderr'] = subprocess.STDOUT
Tao Bao32fcdab2018-10-12 10:30:39 -0700186 # Don't log any if caller explicitly says so.
187 if verbose != False:
188 logger.info(" Running: \"%s\"", " ".join(args))
Doug Zongkereef39442009-04-02 12:14:19 -0700189 return subprocess.Popen(args, **kwargs)
190
191
Tao Bao986ee862018-10-04 15:46:16 -0700192def RunAndCheckOutput(args, verbose=None, **kwargs):
193 """Runs the given command and returns the output.
194
195 Args:
196 args: The command represented as a list of strings.
Tao Bao32fcdab2018-10-12 10:30:39 -0700197 verbose: Whether the commands should be shown. Default to the global
198 verbosity if unspecified.
Tao Bao986ee862018-10-04 15:46:16 -0700199 kwargs: Any additional args to be passed to subprocess.Popen(), such as env,
200 stdin, etc. stdout and stderr will default to subprocess.PIPE and
201 subprocess.STDOUT respectively unless caller specifies any of them.
202
203 Returns:
204 The output string.
205
206 Raises:
207 ExternalError: On non-zero exit from the command.
208 """
Tao Bao986ee862018-10-04 15:46:16 -0700209 proc = Run(args, verbose=verbose, **kwargs)
210 output, _ = proc.communicate()
Tao Bao32fcdab2018-10-12 10:30:39 -0700211 # Don't log any if caller explicitly says so.
212 if verbose != False:
213 logger.info("%s", output.rstrip())
Tao Bao986ee862018-10-04 15:46:16 -0700214 if proc.returncode != 0:
215 raise ExternalError(
216 "Failed to run command '{}' (exit code {}):\n{}".format(
217 args, proc.returncode, output))
218 return output
219
220
Tao Baoc765cca2018-01-31 17:32:40 -0800221def RoundUpTo4K(value):
222 rounded_up = value + 4095
223 return rounded_up - (rounded_up % 4096)
224
225
Ying Wang7e6d4e42010-12-13 16:25:36 -0800226def CloseInheritedPipes():
227 """ Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
228 before doing other work."""
229 if platform.system() != "Darwin":
230 return
231 for d in range(3, 1025):
232 try:
233 stat = os.fstat(d)
234 if stat is not None:
235 pipebit = stat[0] & 0x1000
236 if pipebit != 0:
237 os.close(d)
238 except OSError:
239 pass
240
241
Tao Bao410ad8b2018-08-24 12:08:38 -0700242def LoadInfoDict(input_file, repacking=False):
243 """Loads the key/value pairs from the given input target_files.
244
245 It reads `META/misc_info.txt` file in the target_files input, does sanity
246 checks and returns the parsed key/value pairs for to the given build. It's
247 usually called early when working on input target_files files, e.g. when
248 generating OTAs, or signing builds. Note that the function may be called
249 against an old target_files file (i.e. from past dessert releases). So the
250 property parsing needs to be backward compatible.
251
252 In a `META/misc_info.txt`, a few properties are stored as links to the files
253 in the PRODUCT_OUT directory. It works fine with the build system. However,
254 they are no longer available when (re)generating images from target_files zip.
255 When `repacking` is True, redirect these properties to the actual files in the
256 unzipped directory.
257
258 Args:
259 input_file: The input target_files file, which could be an open
260 zipfile.ZipFile instance, or a str for the dir that contains the files
261 unzipped from a target_files file.
262 repacking: Whether it's trying repack an target_files file after loading the
263 info dict (default: False). If so, it will rewrite a few loaded
264 properties (e.g. selinux_fc, root_dir) to point to the actual files in
265 target_files file. When doing repacking, `input_file` must be a dir.
266
267 Returns:
268 A dict that contains the parsed key/value pairs.
269
270 Raises:
271 AssertionError: On invalid input arguments.
272 ValueError: On malformed input values.
273 """
274 if repacking:
275 assert isinstance(input_file, str), \
276 "input_file must be a path str when doing repacking"
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700277
Doug Zongkerc9253822014-02-04 12:17:58 -0800278 def read_helper(fn):
Dan Albert8b72aef2015-03-23 19:13:21 -0700279 if isinstance(input_file, zipfile.ZipFile):
280 return input_file.read(fn)
Doug Zongkerc9253822014-02-04 12:17:58 -0800281 else:
Dan Albert8b72aef2015-03-23 19:13:21 -0700282 path = os.path.join(input_file, *fn.split("/"))
Doug Zongkerc9253822014-02-04 12:17:58 -0800283 try:
284 with open(path) as f:
285 return f.read()
Dan Albert8b72aef2015-03-23 19:13:21 -0700286 except IOError as e:
Doug Zongkerc9253822014-02-04 12:17:58 -0800287 if e.errno == errno.ENOENT:
288 raise KeyError(fn)
Tao Bao6cd54732017-02-27 15:12:05 -0800289
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700290 try:
Michael Runge6e836112014-04-15 17:40:21 -0700291 d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))
Doug Zongker37974732010-09-16 17:44:38 -0700292 except KeyError:
Tao Bao410ad8b2018-08-24 12:08:38 -0700293 raise ValueError("Failed to find META/misc_info.txt in input target-files")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700294
Tao Bao410ad8b2018-08-24 12:08:38 -0700295 if "recovery_api_version" not in d:
296 raise ValueError("Failed to find 'recovery_api_version'")
297 if "fstab_version" not in d:
298 raise ValueError("Failed to find 'fstab_version'")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800299
Tao Bao410ad8b2018-08-24 12:08:38 -0700300 if repacking:
301 # We carry a copy of file_contexts.bin under META/. If not available, search
302 # BOOT/RAMDISK/. Note that sometimes we may need a different file to build
303 # images than the one running on device, in that case, we must have the one
304 # for image generation copied to META/.
Tao Bao79735a62015-08-28 10:52:03 -0700305 fc_basename = os.path.basename(d.get("selinux_fc", "file_contexts"))
Tao Bao410ad8b2018-08-24 12:08:38 -0700306 fc_config = os.path.join(input_file, "META", fc_basename)
Tom Cherryd14b8952018-08-09 14:26:00 -0700307 assert os.path.exists(fc_config)
Tao Bao2c15d9e2015-07-09 11:51:16 -0700308
Tom Cherryd14b8952018-08-09 14:26:00 -0700309 d["selinux_fc"] = fc_config
Tao Bao2c15d9e2015-07-09 11:51:16 -0700310
Tom Cherryd14b8952018-08-09 14:26:00 -0700311 # Similarly we need to redirect "root_dir", and "root_fs_config".
Tao Bao410ad8b2018-08-24 12:08:38 -0700312 d["root_dir"] = os.path.join(input_file, "ROOT")
Tom Cherryd14b8952018-08-09 14:26:00 -0700313 d["root_fs_config"] = os.path.join(
Tao Bao410ad8b2018-08-24 12:08:38 -0700314 input_file, "META", "root_filesystem_config.txt")
Tao Bao84e75682015-07-19 02:38:53 -0700315
Tao Baof54216f2016-03-29 15:12:37 -0700316 # Redirect {system,vendor}_base_fs_file.
317 if "system_base_fs_file" in d:
318 basename = os.path.basename(d["system_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700319 system_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700320 if os.path.exists(system_base_fs_file):
321 d["system_base_fs_file"] = system_base_fs_file
322 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700323 logger.warning(
324 "Failed to find system base fs file: %s", system_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700325 del d["system_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700326
327 if "vendor_base_fs_file" in d:
328 basename = os.path.basename(d["vendor_base_fs_file"])
Tao Bao410ad8b2018-08-24 12:08:38 -0700329 vendor_base_fs_file = os.path.join(input_file, "META", basename)
Tao Baob079b502016-05-03 08:01:19 -0700330 if os.path.exists(vendor_base_fs_file):
331 d["vendor_base_fs_file"] = vendor_base_fs_file
332 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700333 logger.warning(
334 "Failed to find vendor base fs file: %s", vendor_base_fs_file)
Tao Baob079b502016-05-03 08:01:19 -0700335 del d["vendor_base_fs_file"]
Tao Baof54216f2016-03-29 15:12:37 -0700336
Doug Zongker37974732010-09-16 17:44:38 -0700337 def makeint(key):
338 if key in d:
339 d[key] = int(d[key], 0)
340
341 makeint("recovery_api_version")
342 makeint("blocksize")
343 makeint("system_size")
Daniel Rosenbergf4eabc32014-07-10 15:42:38 -0700344 makeint("vendor_size")
Doug Zongker37974732010-09-16 17:44:38 -0700345 makeint("userdata_size")
Ying Wang9f8e8db2011-11-04 11:37:01 -0700346 makeint("cache_size")
Doug Zongker37974732010-09-16 17:44:38 -0700347 makeint("recovery_size")
348 makeint("boot_size")
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800349 makeint("fstab_version")
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700350
Tao Baoa57ab9f2018-08-24 12:08:38 -0700351 # We changed recovery.fstab path in Q, from ../RAMDISK/etc/recovery.fstab to
352 # ../RAMDISK/system/etc/recovery.fstab. LoadInfoDict() has to handle both
353 # cases, since it may load the info_dict from an old build (e.g. when
354 # generating incremental OTAs from that build).
Tao Bao76def242017-11-21 09:25:31 -0800355 system_root_image = d.get("system_root_image") == "true"
356 if d.get("no_recovery") != "true":
Tao Bao696bb332018-08-17 16:27:01 -0700357 recovery_fstab_path = "RECOVERY/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700358 if isinstance(input_file, zipfile.ZipFile):
359 if recovery_fstab_path not in input_file.namelist():
360 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
361 else:
362 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
363 if not os.path.exists(path):
364 recovery_fstab_path = "RECOVERY/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800365 d["fstab"] = LoadRecoveryFSTab(
366 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700367
Tao Bao76def242017-11-21 09:25:31 -0800368 elif d.get("recovery_as_boot") == "true":
Tao Bao696bb332018-08-17 16:27:01 -0700369 recovery_fstab_path = "BOOT/RAMDISK/system/etc/recovery.fstab"
Tao Baob4adc062018-08-22 18:27:14 -0700370 if isinstance(input_file, zipfile.ZipFile):
371 if recovery_fstab_path not in input_file.namelist():
372 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
373 else:
374 path = os.path.join(input_file, *recovery_fstab_path.split("/"))
375 if not os.path.exists(path):
376 recovery_fstab_path = "BOOT/RAMDISK/etc/recovery.fstab"
Tao Bao76def242017-11-21 09:25:31 -0800377 d["fstab"] = LoadRecoveryFSTab(
378 read_helper, d["fstab_version"], recovery_fstab_path, system_root_image)
Tao Baob4adc062018-08-22 18:27:14 -0700379
Tianjie Xucfa86222016-03-07 16:31:19 -0800380 else:
381 d["fstab"] = None
382
Tianjie Xu861f4132018-09-12 11:49:33 -0700383 # Tries to load the build props for all partitions with care_map, including
384 # system and vendor.
385 for partition in PARTITIONS_WITH_CARE_MAP:
386 d["{}.build.prop".format(partition)] = LoadBuildProp(
387 read_helper, "{}/build.prop".format(partition.upper()))
388 d["build.prop"] = d["system.build.prop"]
Tao Bao12d87fc2018-01-31 12:18:52 -0800389
390 # Set up the salt (based on fingerprint or thumbprint) that will be used when
391 # adding AVB footer.
392 if d.get("avb_enable") == "true":
393 fp = None
394 if "build.prop" in d:
395 build_prop = d["build.prop"]
396 if "ro.build.fingerprint" in build_prop:
397 fp = build_prop["ro.build.fingerprint"]
398 elif "ro.build.thumbprint" in build_prop:
399 fp = build_prop["ro.build.thumbprint"]
400 if fp:
401 d["avb_salt"] = sha256(fp).hexdigest()
402
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700403 return d
404
Tao Baod1de6f32017-03-01 16:38:48 -0800405
Tao Baobcd1d162017-08-26 13:10:26 -0700406def LoadBuildProp(read_helper, prop_file):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700407 try:
Tao Baobcd1d162017-08-26 13:10:26 -0700408 data = read_helper(prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700409 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700410 logger.warning("Failed to read %s", prop_file)
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700411 data = ""
Michael Runge6e836112014-04-15 17:40:21 -0700412 return LoadDictionaryFromLines(data.split("\n"))
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700413
Tao Baod1de6f32017-03-01 16:38:48 -0800414
Michael Runge6e836112014-04-15 17:40:21 -0700415def LoadDictionaryFromLines(lines):
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700416 d = {}
Michael Runge6e836112014-04-15 17:40:21 -0700417 for line in lines:
Doug Zongker1eb74dd2012-08-16 16:19:00 -0700418 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -0700419 if not line or line.startswith("#"):
420 continue
Ying Wang114b46f2014-04-15 11:24:00 -0700421 if "=" in line:
422 name, value = line.split("=", 1)
423 d[name] = value
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700424 return d
425
Tao Baod1de6f32017-03-01 16:38:48 -0800426
Tianjie Xucfa86222016-03-07 16:31:19 -0800427def LoadRecoveryFSTab(read_helper, fstab_version, recovery_fstab_path,
428 system_root_image=False):
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700429 class Partition(object):
Tao Baod1de6f32017-03-01 16:38:48 -0800430 def __init__(self, mount_point, fs_type, device, length, context):
Dan Albert8b72aef2015-03-23 19:13:21 -0700431 self.mount_point = mount_point
432 self.fs_type = fs_type
433 self.device = device
434 self.length = length
Tao Bao548eb762015-06-10 12:32:41 -0700435 self.context = context
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700436
437 try:
Tianjie Xucfa86222016-03-07 16:31:19 -0800438 data = read_helper(recovery_fstab_path)
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700439 except KeyError:
Tao Bao32fcdab2018-10-12 10:30:39 -0700440 logger.warning("Failed to find %s", recovery_fstab_path)
Jeff Davidson033fbe22011-10-26 18:08:09 -0700441 data = ""
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700442
Tao Baod1de6f32017-03-01 16:38:48 -0800443 assert fstab_version == 2
444
445 d = {}
446 for line in data.split("\n"):
447 line = line.strip()
448 if not line or line.startswith("#"):
449 continue
450
451 # <src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
452 pieces = line.split()
453 if len(pieces) != 5:
454 raise ValueError("malformed recovery.fstab line: \"%s\"" % (line,))
455
456 # Ignore entries that are managed by vold.
457 options = pieces[4]
458 if "voldmanaged=" in options:
459 continue
460
461 # It's a good line, parse it.
462 length = 0
463 options = options.split(",")
464 for i in options:
465 if i.startswith("length="):
466 length = int(i[7:])
Doug Zongker086cbb02011-02-17 15:54:20 -0800467 else:
Tao Baod1de6f32017-03-01 16:38:48 -0800468 # Ignore all unknown options in the unified fstab.
Dan Albert8b72aef2015-03-23 19:13:21 -0700469 continue
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800470
Tao Baod1de6f32017-03-01 16:38:48 -0800471 mount_flags = pieces[3]
472 # Honor the SELinux context if present.
473 context = None
474 for i in mount_flags.split(","):
475 if i.startswith("context="):
476 context = i
Doug Zongker086cbb02011-02-17 15:54:20 -0800477
Tao Baod1de6f32017-03-01 16:38:48 -0800478 mount_point = pieces[1]
479 d[mount_point] = Partition(mount_point=mount_point, fs_type=pieces[2],
480 device=pieces[0], length=length, context=context)
Ken Sumrall3b07cf12013-02-19 17:35:29 -0800481
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700482 # / is used for the system mount point when the root directory is included in
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700483 # system. Other areas assume system is always at "/system" so point /system
484 # at /.
Daniel Rosenberge6853b02015-06-05 17:59:27 -0700485 if system_root_image:
486 assert not d.has_key("/system") and d.has_key("/")
487 d["/system"] = d["/"]
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700488 return d
489
490
Doug Zongker37974732010-09-16 17:44:38 -0700491def DumpInfoDict(d):
492 for k, v in sorted(d.items()):
Tao Bao32fcdab2018-10-12 10:30:39 -0700493 logger.info("%-25s = (%s) %s", k, type(v).__name__, v)
Doug Zongkerc19a8d52010-07-01 15:30:11 -0700494
Dan Albert8b72aef2015-03-23 19:13:21 -0700495
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800496def AppendAVBSigningArgs(cmd, partition):
497 """Append signing arguments for avbtool."""
498 # e.g., "--key path/to/signing_key --algorithm SHA256_RSA4096"
499 key_path = OPTIONS.info_dict.get("avb_" + partition + "_key_path")
500 algorithm = OPTIONS.info_dict.get("avb_" + partition + "_algorithm")
501 if key_path and algorithm:
502 cmd.extend(["--key", key_path, "--algorithm", algorithm])
Tao Bao2b6dfd62017-09-27 17:17:43 -0700503 avb_salt = OPTIONS.info_dict.get("avb_salt")
504 # make_vbmeta_image doesn't like "--salt" (and it's not needed).
Tao Bao744c4c72018-08-20 21:09:07 -0700505 if avb_salt and not partition.startswith("vbmeta"):
Tao Bao2b6dfd62017-09-27 17:17:43 -0700506 cmd.extend(["--salt", avb_salt])
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800507
508
Tao Bao02a08592018-07-22 12:40:45 -0700509def GetAvbChainedPartitionArg(partition, info_dict, key=None):
510 """Constructs and returns the arg to build or verify a chained partition.
511
512 Args:
513 partition: The partition name.
514 info_dict: The info dict to look up the key info and rollback index
515 location.
516 key: The key to be used for building or verifying the partition. Defaults to
517 the key listed in info_dict.
518
519 Returns:
520 A string of form "partition:rollback_index_location:key" that can be used to
521 build or verify vbmeta image.
Tao Bao02a08592018-07-22 12:40:45 -0700522 """
523 if key is None:
524 key = info_dict["avb_" + partition + "_key_path"]
525 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
526 pubkey_path = MakeTempFile(prefix="avb-", suffix=".pubkey")
Tao Bao986ee862018-10-04 15:46:16 -0700527 RunAndCheckOutput(
Tao Bao73dd4f42018-10-04 16:25:33 -0700528 [avbtool, "extract_public_key", "--key", key, "--output", pubkey_path])
Tao Bao02a08592018-07-22 12:40:45 -0700529
530 rollback_index_location = info_dict[
531 "avb_" + partition + "_rollback_index_location"]
532 return "{}:{}:{}".format(partition, rollback_index_location, pubkey_path)
533
534
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700535def _BuildBootableImage(sourcedir, fs_config_file, info_dict=None,
Tao Baod42e97e2016-11-30 12:11:57 -0800536 has_ramdisk=False, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700537 """Build a bootable image from the specified sourcedir.
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700538
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700539 Take a kernel, cmdline, and optionally a ramdisk directory from the input (in
Tao Baod42e97e2016-11-30 12:11:57 -0800540 'sourcedir'), and turn them into a boot image. 'two_step_image' indicates if
541 we are building a two-step special image (i.e. building a recovery image to
542 be loaded into /boot in two-step OTAs).
543
544 Return the image data, or None if sourcedir does not appear to contains files
545 for building the requested image.
546 """
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700547
548 def make_ramdisk():
549 ramdisk_img = tempfile.NamedTemporaryFile()
550
551 if os.access(fs_config_file, os.F_OK):
552 cmd = ["mkbootfs", "-f", fs_config_file,
553 os.path.join(sourcedir, "RAMDISK")]
554 else:
555 cmd = ["mkbootfs", os.path.join(sourcedir, "RAMDISK")]
556 p1 = Run(cmd, stdout=subprocess.PIPE)
557 p2 = Run(["minigzip"], stdin=p1.stdout, stdout=ramdisk_img.file.fileno())
558
559 p2.wait()
560 p1.wait()
561 assert p1.returncode == 0, "mkbootfs of %s ramdisk failed" % (sourcedir,)
562 assert p2.returncode == 0, "minigzip of %s ramdisk failed" % (sourcedir,)
563
564 return ramdisk_img
565
566 if not os.access(os.path.join(sourcedir, "kernel"), os.F_OK):
567 return None
568
569 if has_ramdisk and not os.access(os.path.join(sourcedir, "RAMDISK"), os.F_OK):
Doug Zongkere1c31ba2009-06-23 17:40:35 -0700570 return None
Doug Zongkereef39442009-04-02 12:14:19 -0700571
Doug Zongkerd5131602012-08-02 14:46:42 -0700572 if info_dict is None:
573 info_dict = OPTIONS.info_dict
574
Doug Zongkereef39442009-04-02 12:14:19 -0700575 img = tempfile.NamedTemporaryFile()
576
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700577 if has_ramdisk:
578 ramdisk_img = make_ramdisk()
Doug Zongkereef39442009-04-02 12:14:19 -0700579
Bjorn Andersson612e2cd2012-11-25 16:53:44 -0800580 # use MKBOOTIMG from environ, or "mkbootimg" if empty or not set
581 mkbootimg = os.getenv('MKBOOTIMG') or "mkbootimg"
582
583 cmd = [mkbootimg, "--kernel", os.path.join(sourcedir, "kernel")]
Doug Zongker38a649f2009-06-17 09:07:09 -0700584
Benoit Fradina45a8682014-07-14 21:00:43 +0200585 fn = os.path.join(sourcedir, "second")
586 if os.access(fn, os.F_OK):
587 cmd.append("--second")
588 cmd.append(fn)
589
Hridya Valsaraju9683b2f2019-01-22 18:08:59 -0800590 fn = os.path.join(sourcedir, "dtb")
591 if os.access(fn, os.F_OK):
592 cmd.append("--dtb")
593 cmd.append(fn)
594
Doug Zongker171f1cd2009-06-15 22:36:37 -0700595 fn = os.path.join(sourcedir, "cmdline")
596 if os.access(fn, os.F_OK):
Doug Zongker38a649f2009-06-17 09:07:09 -0700597 cmd.append("--cmdline")
598 cmd.append(open(fn).read().rstrip("\n"))
599
600 fn = os.path.join(sourcedir, "base")
601 if os.access(fn, os.F_OK):
602 cmd.append("--base")
603 cmd.append(open(fn).read().rstrip("\n"))
604
Ying Wang4de6b5b2010-08-25 14:29:34 -0700605 fn = os.path.join(sourcedir, "pagesize")
606 if os.access(fn, os.F_OK):
607 cmd.append("--pagesize")
608 cmd.append(open(fn).read().rstrip("\n"))
609
Tao Bao76def242017-11-21 09:25:31 -0800610 args = info_dict.get("mkbootimg_args")
Doug Zongkerd5131602012-08-02 14:46:42 -0700611 if args and args.strip():
Jianxun Zhang09849492013-04-17 15:19:19 -0700612 cmd.extend(shlex.split(args))
Doug Zongkerd5131602012-08-02 14:46:42 -0700613
Tao Bao76def242017-11-21 09:25:31 -0800614 args = info_dict.get("mkbootimg_version_args")
Sami Tolvanen3303d902016-03-15 16:49:30 +0000615 if args and args.strip():
616 cmd.extend(shlex.split(args))
617
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700618 if has_ramdisk:
619 cmd.extend(["--ramdisk", ramdisk_img.name])
620
Tao Baod95e9fd2015-03-29 23:07:41 -0700621 img_unsigned = None
Tao Bao76def242017-11-21 09:25:31 -0800622 if info_dict.get("vboot"):
Tao Baod95e9fd2015-03-29 23:07:41 -0700623 img_unsigned = tempfile.NamedTemporaryFile()
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700624 cmd.extend(["--output", img_unsigned.name])
Tao Baod95e9fd2015-03-29 23:07:41 -0700625 else:
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700626 cmd.extend(["--output", img.name])
Doug Zongker38a649f2009-06-17 09:07:09 -0700627
Tao Baobf70c3182017-07-11 17:27:55 -0700628 # "boot" or "recovery", without extension.
629 partition_name = os.path.basename(sourcedir).lower()
630
Chen, ZhiminX752439b2018-09-23 22:10:47 +0800631 if partition_name == "recovery":
632 if info_dict.get("include_recovery_dtbo") == "true":
633 fn = os.path.join(sourcedir, "recovery_dtbo")
634 cmd.extend(["--recovery_dtbo", fn])
635 if info_dict.get("include_recovery_acpio") == "true":
636 fn = os.path.join(sourcedir, "recovery_acpio")
637 cmd.extend(["--recovery_acpio", fn])
Hridya Valsarajue74a38b2018-03-21 12:15:11 -0700638
Tao Bao986ee862018-10-04 15:46:16 -0700639 RunAndCheckOutput(cmd)
Doug Zongkereef39442009-04-02 12:14:19 -0700640
Tao Bao76def242017-11-21 09:25:31 -0800641 if (info_dict.get("boot_signer") == "true" and
642 info_dict.get("verity_key")):
Tao Baod42e97e2016-11-30 12:11:57 -0800643 # Hard-code the path as "/boot" for two-step special recovery image (which
644 # will be loaded into /boot during the two-step OTA).
645 if two_step_image:
646 path = "/boot"
647 else:
Tao Baobf70c3182017-07-11 17:27:55 -0700648 path = "/" + partition_name
Baligh Uddin601ddea2015-06-09 15:48:14 -0700649 cmd = [OPTIONS.boot_signer_path]
650 cmd.extend(OPTIONS.boot_signer_args)
651 cmd.extend([path, img.name,
652 info_dict["verity_key"] + ".pk8",
653 info_dict["verity_key"] + ".x509.pem", img.name])
Tao Bao986ee862018-10-04 15:46:16 -0700654 RunAndCheckOutput(cmd)
Geremy Condra95ebe7a2014-08-19 17:27:56 -0700655
Tao Baod95e9fd2015-03-29 23:07:41 -0700656 # Sign the image if vboot is non-empty.
Tao Bao76def242017-11-21 09:25:31 -0800657 elif info_dict.get("vboot"):
Tao Baobf70c3182017-07-11 17:27:55 -0700658 path = "/" + partition_name
Tao Baod95e9fd2015-03-29 23:07:41 -0700659 img_keyblock = tempfile.NamedTemporaryFile()
Tao Bao4f104d12017-02-17 23:21:31 -0800660 # We have switched from the prebuilt futility binary to using the tool
661 # (futility-host) built from the source. Override the setting in the old
662 # TF.zip.
663 futility = info_dict["futility"]
664 if futility.startswith("prebuilts/"):
665 futility = "futility-host"
666 cmd = [info_dict["vboot_signer_cmd"], futility,
Tao Baod95e9fd2015-03-29 23:07:41 -0700667 img_unsigned.name, info_dict["vboot_key"] + ".vbpubk",
Furquan Shaikh852b8de2015-08-10 11:43:45 -0700668 info_dict["vboot_key"] + ".vbprivk",
669 info_dict["vboot_subkey"] + ".vbprivk",
670 img_keyblock.name,
Tao Baod95e9fd2015-03-29 23:07:41 -0700671 img.name]
Tao Bao986ee862018-10-04 15:46:16 -0700672 RunAndCheckOutput(cmd)
Tao Baod95e9fd2015-03-29 23:07:41 -0700673
Tao Baof3282b42015-04-01 11:21:55 -0700674 # Clean up the temp files.
675 img_unsigned.close()
676 img_keyblock.close()
677
David Zeuthen8fecb282017-12-01 16:24:01 -0500678 # AVB: if enabled, calculate and add hash to boot.img or recovery.img.
Bowgo Tsai3e599ea2017-05-26 18:30:04 +0800679 if info_dict.get("avb_enable") == "true":
Tao Bao3ebfdde2017-05-23 23:06:55 -0700680 avbtool = os.getenv('AVBTOOL') or info_dict["avb_avbtool"]
David Zeuthen8fecb282017-12-01 16:24:01 -0500681 part_size = info_dict[partition_name + "_size"]
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400682 cmd = [avbtool, "add_hash_footer", "--image", img.name,
Tao Baobf70c3182017-07-11 17:27:55 -0700683 "--partition_size", str(part_size), "--partition_name",
684 partition_name]
685 AppendAVBSigningArgs(cmd, partition_name)
David Zeuthen8fecb282017-12-01 16:24:01 -0500686 args = info_dict.get("avb_" + partition_name + "_add_hash_footer_args")
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400687 if args and args.strip():
688 cmd.extend(shlex.split(args))
Tao Bao986ee862018-10-04 15:46:16 -0700689 RunAndCheckOutput(cmd)
David Zeuthend995f4b2016-01-29 16:59:17 -0500690
691 img.seek(os.SEEK_SET, 0)
692 data = img.read()
693
694 if has_ramdisk:
695 ramdisk_img.close()
696 img.close()
697
698 return data
699
700
Doug Zongkerd5131602012-08-02 14:46:42 -0700701def GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir,
Tao Baod42e97e2016-11-30 12:11:57 -0800702 info_dict=None, two_step_image=False):
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700703 """Return a File object with the desired bootable image.
704
705 Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name 'prebuilt_name',
706 otherwise look for it under 'unpack_dir'/IMAGES, otherwise construct it from
707 the source files in 'unpack_dir'/'tree_subdir'."""
Doug Zongkereef39442009-04-02 12:14:19 -0700708
Doug Zongker55d93282011-01-25 17:03:34 -0800709 prebuilt_path = os.path.join(unpack_dir, "BOOTABLE_IMAGES", prebuilt_name)
710 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700711 logger.info("using prebuilt %s from BOOTABLE_IMAGES...", prebuilt_name)
Doug Zongker55d93282011-01-25 17:03:34 -0800712 return File.FromLocalFile(name, prebuilt_path)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700713
714 prebuilt_path = os.path.join(unpack_dir, "IMAGES", prebuilt_name)
715 if os.path.exists(prebuilt_path):
Tao Bao32fcdab2018-10-12 10:30:39 -0700716 logger.info("using prebuilt %s from IMAGES...", prebuilt_name)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700717 return File.FromLocalFile(name, prebuilt_path)
718
Tao Bao32fcdab2018-10-12 10:30:39 -0700719 logger.info("building image from target_files %s...", tree_subdir)
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700720
721 if info_dict is None:
722 info_dict = OPTIONS.info_dict
723
724 # With system_root_image == "true", we don't pack ramdisk into the boot image.
Daniel Rosenberg40ef35b2015-11-10 19:21:34 -0800725 # Unless "recovery_as_boot" is specified, in which case we carry the ramdisk
726 # for recovery.
727 has_ramdisk = (info_dict.get("system_root_image") != "true" or
728 prebuilt_name != "boot.img" or
729 info_dict.get("recovery_as_boot") == "true")
Tao Bao7a5bf8a2015-07-21 18:01:20 -0700730
Doug Zongker6f1d0312014-08-22 08:07:12 -0700731 fs_config = "META/" + tree_subdir.lower() + "_filesystem_config.txt"
David Zeuthen2ce63ed2016-09-15 13:43:54 -0400732 data = _BuildBootableImage(os.path.join(unpack_dir, tree_subdir),
733 os.path.join(unpack_dir, fs_config),
Tao Baod42e97e2016-11-30 12:11:57 -0800734 info_dict, has_ramdisk, two_step_image)
Doug Zongker6f1d0312014-08-22 08:07:12 -0700735 if data:
736 return File(name, data)
737 return None
Doug Zongker55d93282011-01-25 17:03:34 -0800738
Doug Zongkereef39442009-04-02 12:14:19 -0700739
Narayan Kamatha07bf042017-08-14 14:49:21 +0100740def Gunzip(in_filename, out_filename):
Tao Bao76def242017-11-21 09:25:31 -0800741 """Gunzips the given gzip compressed file to a given output file."""
742 with gzip.open(in_filename, "rb") as in_file, \
743 open(out_filename, "wb") as out_file:
Narayan Kamatha07bf042017-08-14 14:49:21 +0100744 shutil.copyfileobj(in_file, out_file)
745
746
Doug Zongker75f17362009-12-08 13:46:44 -0800747def UnzipTemp(filename, pattern=None):
Tao Bao1c830bf2017-12-25 10:43:47 -0800748 """Unzips the given archive into a temporary directory and returns the name.
Doug Zongker55d93282011-01-25 17:03:34 -0800749
Tao Bao1c830bf2017-12-25 10:43:47 -0800750 If filename is of the form "foo.zip+bar.zip", unzip foo.zip into a temp dir,
751 then unzip bar.zip into that_dir/BOOTABLE_IMAGES.
Doug Zongker55d93282011-01-25 17:03:34 -0800752
Tao Bao1c830bf2017-12-25 10:43:47 -0800753 Returns:
Tao Baodba59ee2018-01-09 13:21:02 -0800754 The name of the temporary directory.
Doug Zongker55d93282011-01-25 17:03:34 -0800755 """
Doug Zongkereef39442009-04-02 12:14:19 -0700756
Doug Zongker55d93282011-01-25 17:03:34 -0800757 def unzip_to_dir(filename, dirname):
758 cmd = ["unzip", "-o", "-q", filename, "-d", dirname]
759 if pattern is not None:
Tao Bao6b0b2f92017-03-05 11:38:11 -0800760 cmd.extend(pattern)
Tao Bao986ee862018-10-04 15:46:16 -0700761 RunAndCheckOutput(cmd)
Doug Zongker55d93282011-01-25 17:03:34 -0800762
Tao Bao1c830bf2017-12-25 10:43:47 -0800763 tmp = MakeTempDir(prefix="targetfiles-")
Doug Zongker55d93282011-01-25 17:03:34 -0800764 m = re.match(r"^(.*[.]zip)\+(.*[.]zip)$", filename, re.IGNORECASE)
765 if m:
766 unzip_to_dir(m.group(1), tmp)
767 unzip_to_dir(m.group(2), os.path.join(tmp, "BOOTABLE_IMAGES"))
768 filename = m.group(1)
769 else:
770 unzip_to_dir(filename, tmp)
771
Tao Baodba59ee2018-01-09 13:21:02 -0800772 return tmp
Doug Zongkereef39442009-04-02 12:14:19 -0700773
774
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700775def GetSparseImage(which, tmpdir, input_zip, allow_shared_blocks,
776 hashtree_info_generator=None):
Tao Baoc765cca2018-01-31 17:32:40 -0800777 """Returns a SparseImage object suitable for passing to BlockImageDiff.
778
779 This function loads the specified sparse image from the given path, and
780 performs additional processing for OTA purpose. For example, it always adds
781 block 0 to clobbered blocks list. It also detects files that cannot be
782 reconstructed from the block list, for whom we should avoid applying imgdiff.
783
784 Args:
785 which: The partition name, which must be "system" or "vendor".
786 tmpdir: The directory that contains the prebuilt image and block map file.
787 input_zip: The target-files ZIP archive.
Tao Baoe709b092018-02-07 12:40:00 -0800788 allow_shared_blocks: Whether having shared blocks is allowed.
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700789 hashtree_info_generator: If present, generates the hashtree_info for this
790 sparse image.
Tao Baoc765cca2018-01-31 17:32:40 -0800791 Returns:
792 A SparseImage object, with file_map info loaded.
793 """
794 assert which in ("system", "vendor")
795
796 path = os.path.join(tmpdir, "IMAGES", which + ".img")
797 mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
798
799 # The image and map files must have been created prior to calling
800 # ota_from_target_files.py (since LMP).
801 assert os.path.exists(path) and os.path.exists(mappath)
802
803 # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
804 # it to clobbered_blocks so that it will be written to the target
805 # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
806 clobbered_blocks = "0"
807
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700808 image = sparse_img.SparseImage(
809 path, mappath, clobbered_blocks, allow_shared_blocks=allow_shared_blocks,
810 hashtree_info_generator=hashtree_info_generator)
Tao Baoc765cca2018-01-31 17:32:40 -0800811
812 # block.map may contain less blocks, because mke2fs may skip allocating blocks
813 # if they contain all zeros. We can't reconstruct such a file from its block
814 # list. Tag such entries accordingly. (Bug: 65213616)
815 for entry in image.file_map:
Tao Baoc765cca2018-01-31 17:32:40 -0800816 # Skip artificial names, such as "__ZERO", "__NONZERO-1".
Tao Baod3554e62018-07-10 15:31:22 -0700817 if not entry.startswith('/'):
Tao Baoc765cca2018-01-31 17:32:40 -0800818 continue
819
Tom Cherryd14b8952018-08-09 14:26:00 -0700820 # "/system/framework/am.jar" => "SYSTEM/framework/am.jar". Note that the
821 # filename listed in system.map may contain an additional leading slash
822 # (i.e. "//system/framework/am.jar"). Using lstrip to get consistent
823 # results.
Tao Baod3554e62018-07-10 15:31:22 -0700824 arcname = string.replace(entry, which, which.upper(), 1).lstrip('/')
825
Tom Cherryd14b8952018-08-09 14:26:00 -0700826 # Special handling another case, where files not under /system
827 # (e.g. "/sbin/charger") are packed under ROOT/ in a target_files.zip.
Tao Baod3554e62018-07-10 15:31:22 -0700828 if which == 'system' and not arcname.startswith('SYSTEM'):
829 arcname = 'ROOT/' + arcname
830
831 assert arcname in input_zip.namelist(), \
832 "Failed to find the ZIP entry for {}".format(entry)
833
Tao Baoc765cca2018-01-31 17:32:40 -0800834 info = input_zip.getinfo(arcname)
835 ranges = image.file_map[entry]
Tao Baoe709b092018-02-07 12:40:00 -0800836
837 # If a RangeSet has been tagged as using shared blocks while loading the
Tao Bao2a20f342018-12-03 15:08:23 -0800838 # image, check the original block list to determine its completeness. Note
839 # that the 'incomplete' flag would be tagged to the original RangeSet only.
Tao Baoe709b092018-02-07 12:40:00 -0800840 if ranges.extra.get('uses_shared_blocks'):
Tao Bao2a20f342018-12-03 15:08:23 -0800841 ranges = ranges.extra['uses_shared_blocks']
Tao Baoe709b092018-02-07 12:40:00 -0800842
Tao Baoc765cca2018-01-31 17:32:40 -0800843 if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
844 ranges.extra['incomplete'] = True
845
846 return image
847
848
Doug Zongkereef39442009-04-02 12:14:19 -0700849def GetKeyPasswords(keylist):
850 """Given a list of keys, prompt the user to enter passwords for
851 those which require them. Return a {key: password} dict. password
852 will be None if the key has no password."""
853
Doug Zongker8ce7c252009-05-22 13:34:54 -0700854 no_passwords = []
855 need_passwords = []
T.R. Fullhart37e10522013-03-18 10:31:26 -0700856 key_passwords = {}
Doug Zongkereef39442009-04-02 12:14:19 -0700857 devnull = open("/dev/null", "w+b")
858 for k in sorted(keylist):
Doug Zongkerf6a53aa2009-12-15 15:06:55 -0800859 # We don't need a password for things that aren't really keys.
860 if k in SPECIAL_CERT_STRINGS:
Doug Zongker8ce7c252009-05-22 13:34:54 -0700861 no_passwords.append(k)
Doug Zongker43874f82009-04-14 14:05:15 -0700862 continue
863
T.R. Fullhart37e10522013-03-18 10:31:26 -0700864 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
Doug Zongker602a84e2009-06-18 08:35:12 -0700865 "-inform", "DER", "-nocrypt"],
866 stdin=devnull.fileno(),
867 stdout=devnull.fileno(),
868 stderr=subprocess.STDOUT)
Doug Zongkereef39442009-04-02 12:14:19 -0700869 p.communicate()
870 if p.returncode == 0:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700871 # Definitely an unencrypted key.
Doug Zongker8ce7c252009-05-22 13:34:54 -0700872 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700873 else:
T.R. Fullhart37e10522013-03-18 10:31:26 -0700874 p = Run(["openssl", "pkcs8", "-in", k+OPTIONS.private_key_suffix,
875 "-inform", "DER", "-passin", "pass:"],
876 stdin=devnull.fileno(),
877 stdout=devnull.fileno(),
878 stderr=subprocess.PIPE)
Dan Albert8b72aef2015-03-23 19:13:21 -0700879 _, stderr = p.communicate()
T.R. Fullhart37e10522013-03-18 10:31:26 -0700880 if p.returncode == 0:
881 # Encrypted key with empty string as password.
882 key_passwords[k] = ''
883 elif stderr.startswith('Error decrypting key'):
884 # Definitely encrypted key.
885 # It would have said "Error reading key" if it didn't parse correctly.
886 need_passwords.append(k)
887 else:
888 # Potentially, a type of key that openssl doesn't understand.
889 # We'll let the routines in signapk.jar handle it.
890 no_passwords.append(k)
Doug Zongkereef39442009-04-02 12:14:19 -0700891 devnull.close()
Doug Zongker8ce7c252009-05-22 13:34:54 -0700892
T.R. Fullhart37e10522013-03-18 10:31:26 -0700893 key_passwords.update(PasswordManager().GetPasswords(need_passwords))
Tao Bao76def242017-11-21 09:25:31 -0800894 key_passwords.update(dict.fromkeys(no_passwords))
Doug Zongkereef39442009-04-02 12:14:19 -0700895 return key_passwords
896
897
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800898def GetMinSdkVersion(apk_name):
Tao Baof47bf0f2018-03-21 23:28:51 -0700899 """Gets the minSdkVersion declared in the APK.
900
901 It calls 'aapt' to query the embedded minSdkVersion from the given APK file.
902 This can be both a decimal number (API Level) or a codename.
903
904 Args:
905 apk_name: The APK filename.
906
907 Returns:
908 The parsed SDK version string.
909
910 Raises:
911 ExternalError: On failing to obtain the min SDK version.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800912 """
Tao Baof47bf0f2018-03-21 23:28:51 -0700913 proc = Run(
914 ["aapt", "dump", "badging", apk_name], stdout=subprocess.PIPE,
915 stderr=subprocess.PIPE)
916 stdoutdata, stderrdata = proc.communicate()
917 if proc.returncode != 0:
918 raise ExternalError(
919 "Failed to obtain minSdkVersion: aapt return code {}:\n{}\n{}".format(
920 proc.returncode, stdoutdata, stderrdata))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800921
Tao Baof47bf0f2018-03-21 23:28:51 -0700922 for line in stdoutdata.split("\n"):
923 # Looking for lines such as sdkVersion:'23' or sdkVersion:'M'.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800924 m = re.match(r'sdkVersion:\'([^\']*)\'', line)
925 if m:
926 return m.group(1)
927 raise ExternalError("No minSdkVersion returned by aapt")
928
929
930def GetMinSdkVersionInt(apk_name, codename_to_api_level_map):
Tao Baof47bf0f2018-03-21 23:28:51 -0700931 """Returns the minSdkVersion declared in the APK as a number (API Level).
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800932
Tao Baof47bf0f2018-03-21 23:28:51 -0700933 If minSdkVersion is set to a codename, it is translated to a number using the
934 provided map.
935
936 Args:
937 apk_name: The APK filename.
938
939 Returns:
940 The parsed SDK version number.
941
942 Raises:
943 ExternalError: On failing to get the min SDK version number.
944 """
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800945 version = GetMinSdkVersion(apk_name)
946 try:
947 return int(version)
948 except ValueError:
949 # Not a decimal number. Codename?
950 if version in codename_to_api_level_map:
951 return codename_to_api_level_map[version]
952 else:
Tao Baof47bf0f2018-03-21 23:28:51 -0700953 raise ExternalError(
954 "Unknown minSdkVersion: '{}'. Known codenames: {}".format(
955 version, codename_to_api_level_map))
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800956
957
958def SignFile(input_name, output_name, key, password, min_api_level=None,
Tao Bao76def242017-11-21 09:25:31 -0800959 codename_to_api_level_map=None, whole_file=False):
Doug Zongkereef39442009-04-02 12:14:19 -0700960 """Sign the input_name zip/jar/apk, producing output_name. Use the
961 given key and password (the latter may be None if the key does not
962 have a password.
963
Doug Zongker951495f2009-08-14 12:44:19 -0700964 If whole_file is true, use the "-w" option to SignApk to embed a
965 signature that covers the whole file in the archive comment of the
966 zip file.
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800967
968 min_api_level is the API Level (int) of the oldest platform this file may end
969 up on. If not specified for an APK, the API Level is obtained by interpreting
970 the minSdkVersion attribute of the APK's AndroidManifest.xml.
971
972 codename_to_api_level_map is needed to translate the codename which may be
973 encountered as the APK's minSdkVersion.
Doug Zongkereef39442009-04-02 12:14:19 -0700974 """
Tao Bao76def242017-11-21 09:25:31 -0800975 if codename_to_api_level_map is None:
976 codename_to_api_level_map = {}
Doug Zongker951495f2009-08-14 12:44:19 -0700977
Alex Klyubin9667b182015-12-10 13:38:50 -0800978 java_library_path = os.path.join(
979 OPTIONS.search_path, OPTIONS.signapk_shared_library_path)
980
Tao Baoe95540e2016-11-08 12:08:53 -0800981 cmd = ([OPTIONS.java_path] + OPTIONS.java_args +
982 ["-Djava.library.path=" + java_library_path,
983 "-jar", os.path.join(OPTIONS.search_path, OPTIONS.signapk_path)] +
984 OPTIONS.extra_signapk_args)
Doug Zongker951495f2009-08-14 12:44:19 -0700985 if whole_file:
986 cmd.append("-w")
Alex Klyubin2cfd1d12016-01-13 10:32:47 -0800987
988 min_sdk_version = min_api_level
989 if min_sdk_version is None:
990 if not whole_file:
991 min_sdk_version = GetMinSdkVersionInt(
992 input_name, codename_to_api_level_map)
993 if min_sdk_version is not None:
994 cmd.extend(["--min-sdk-version", str(min_sdk_version)])
995
T.R. Fullhart37e10522013-03-18 10:31:26 -0700996 cmd.extend([key + OPTIONS.public_key_suffix,
997 key + OPTIONS.private_key_suffix,
Alex Klyubineb756d72015-12-04 09:21:08 -0800998 input_name, output_name])
Doug Zongker951495f2009-08-14 12:44:19 -0700999
Tao Bao73dd4f42018-10-04 16:25:33 -07001000 proc = Run(cmd, stdin=subprocess.PIPE)
Doug Zongkereef39442009-04-02 12:14:19 -07001001 if password is not None:
1002 password += "\n"
Tao Bao73dd4f42018-10-04 16:25:33 -07001003 stdoutdata, _ = proc.communicate(password)
1004 if proc.returncode != 0:
Tao Bao80921982018-03-21 21:02:19 -07001005 raise ExternalError(
1006 "Failed to run signapk.jar: return code {}:\n{}".format(
Tao Bao73dd4f42018-10-04 16:25:33 -07001007 proc.returncode, stdoutdata))
Doug Zongkereef39442009-04-02 12:14:19 -07001008
Doug Zongkereef39442009-04-02 12:14:19 -07001009
Doug Zongker37974732010-09-16 17:44:38 -07001010def CheckSize(data, target, info_dict):
Tao Bao9dd909e2017-11-14 11:27:32 -08001011 """Checks the data string passed against the max size limit.
Doug Zongkerc77a9ad2010-09-16 11:28:43 -07001012
Tao Bao9dd909e2017-11-14 11:27:32 -08001013 For non-AVB images, raise exception if the data is too big. Print a warning
1014 if the data is nearing the maximum size.
1015
1016 For AVB images, the actual image size should be identical to the limit.
1017
1018 Args:
1019 data: A string that contains all the data for the partition.
1020 target: The partition name. The ".img" suffix is optional.
1021 info_dict: The dict to be looked up for relevant info.
1022 """
Dan Albert8b72aef2015-03-23 19:13:21 -07001023 if target.endswith(".img"):
1024 target = target[:-4]
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001025 mount_point = "/" + target
1026
Ying Wangf8824af2014-06-03 14:07:27 -07001027 fs_type = None
1028 limit = None
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001029 if info_dict["fstab"]:
Dan Albert8b72aef2015-03-23 19:13:21 -07001030 if mount_point == "/userdata":
1031 mount_point = "/data"
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001032 p = info_dict["fstab"][mount_point]
1033 fs_type = p.fs_type
Andrew Boie0f9aec82012-02-14 09:32:52 -08001034 device = p.device
1035 if "/" in device:
1036 device = device[device.rfind("/")+1:]
Tao Bao76def242017-11-21 09:25:31 -08001037 limit = info_dict.get(device + "_size")
Dan Albert8b72aef2015-03-23 19:13:21 -07001038 if not fs_type or not limit:
1039 return
Doug Zongkereef39442009-04-02 12:14:19 -07001040
Andrew Boie0f9aec82012-02-14 09:32:52 -08001041 size = len(data)
Tao Bao9dd909e2017-11-14 11:27:32 -08001042 # target could be 'userdata' or 'cache'. They should follow the non-AVB image
1043 # path.
1044 if info_dict.get("avb_enable") == "true" and target in AVB_PARTITIONS:
1045 if size != limit:
1046 raise ExternalError(
1047 "Mismatching image size for %s: expected %d actual %d" % (
1048 target, limit, size))
1049 else:
1050 pct = float(size) * 100.0 / limit
1051 msg = "%s size (%d) is %.2f%% of limit (%d)" % (target, size, pct, limit)
1052 if pct >= 99.0:
1053 raise ExternalError(msg)
1054 elif pct >= 95.0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001055 logger.warning("\n WARNING: %s\n", msg)
1056 else:
1057 logger.info(" %s", msg)
Doug Zongkereef39442009-04-02 12:14:19 -07001058
1059
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001060def ReadApkCerts(tf_zip):
Tao Bao818ddf52018-01-05 11:17:34 -08001061 """Parses the APK certs info from a given target-files zip.
1062
1063 Given a target-files ZipFile, parses the META/apkcerts.txt entry and returns a
1064 tuple with the following elements: (1) a dictionary that maps packages to
1065 certs (based on the "certificate" and "private_key" attributes in the file;
1066 (2) a string representing the extension of compressed APKs in the target files
1067 (e.g ".gz", ".bro").
1068
1069 Args:
1070 tf_zip: The input target_files ZipFile (already open).
1071
1072 Returns:
1073 (certmap, ext): certmap is a dictionary that maps packages to certs; ext is
1074 the extension string of compressed APKs (e.g. ".gz"), or None if there's
1075 no compressed APKs.
1076 """
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001077 certmap = {}
Narayan Kamatha07bf042017-08-14 14:49:21 +01001078 compressed_extension = None
1079
Tao Bao0f990332017-09-08 19:02:54 -07001080 # META/apkcerts.txt contains the info for _all_ the packages known at build
1081 # time. Filter out the ones that are not installed.
1082 installed_files = set()
1083 for name in tf_zip.namelist():
1084 basename = os.path.basename(name)
1085 if basename:
1086 installed_files.add(basename)
1087
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001088 for line in tf_zip.read("META/apkcerts.txt").split("\n"):
1089 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001090 if not line:
1091 continue
Tao Bao818ddf52018-01-05 11:17:34 -08001092 m = re.match(
1093 r'^name="(?P<NAME>.*)"\s+certificate="(?P<CERT>.*)"\s+'
1094 r'private_key="(?P<PRIVKEY>.*?)"(\s+compressed="(?P<COMPRESSED>.*)")?$',
1095 line)
1096 if not m:
1097 continue
Narayan Kamatha07bf042017-08-14 14:49:21 +01001098
Tao Bao818ddf52018-01-05 11:17:34 -08001099 matches = m.groupdict()
1100 cert = matches["CERT"]
1101 privkey = matches["PRIVKEY"]
1102 name = matches["NAME"]
1103 this_compressed_extension = matches["COMPRESSED"]
1104
1105 public_key_suffix_len = len(OPTIONS.public_key_suffix)
1106 private_key_suffix_len = len(OPTIONS.private_key_suffix)
1107 if cert in SPECIAL_CERT_STRINGS and not privkey:
1108 certmap[name] = cert
1109 elif (cert.endswith(OPTIONS.public_key_suffix) and
1110 privkey.endswith(OPTIONS.private_key_suffix) and
1111 cert[:-public_key_suffix_len] == privkey[:-private_key_suffix_len]):
1112 certmap[name] = cert[:-public_key_suffix_len]
1113 else:
1114 raise ValueError("Failed to parse line from apkcerts.txt:\n" + line)
1115
1116 if not this_compressed_extension:
1117 continue
1118
1119 # Only count the installed files.
1120 filename = name + '.' + this_compressed_extension
1121 if filename not in installed_files:
1122 continue
1123
1124 # Make sure that all the values in the compression map have the same
1125 # extension. We don't support multiple compression methods in the same
1126 # system image.
1127 if compressed_extension:
1128 if this_compressed_extension != compressed_extension:
1129 raise ValueError(
1130 "Multiple compressed extensions: {} vs {}".format(
1131 compressed_extension, this_compressed_extension))
1132 else:
1133 compressed_extension = this_compressed_extension
1134
1135 return (certmap,
1136 ("." + compressed_extension) if compressed_extension else None)
Doug Zongkerf6a53aa2009-12-15 15:06:55 -08001137
1138
Doug Zongkereef39442009-04-02 12:14:19 -07001139COMMON_DOCSTRING = """
Tao Bao30df8b42018-04-23 15:32:53 -07001140Global options
1141
1142 -p (--path) <dir>
1143 Prepend <dir>/bin to the list of places to search for binaries run by this
1144 script, and expect to find jars in <dir>/framework.
Doug Zongkereef39442009-04-02 12:14:19 -07001145
Doug Zongker05d3dea2009-06-22 11:32:31 -07001146 -s (--device_specific) <file>
Tao Bao30df8b42018-04-23 15:32:53 -07001147 Path to the Python module containing device-specific releasetools code.
Doug Zongker05d3dea2009-06-22 11:32:31 -07001148
Tao Bao30df8b42018-04-23 15:32:53 -07001149 -x (--extra) <key=value>
1150 Add a key/value pair to the 'extras' dict, which device-specific extension
1151 code may look at.
Doug Zongker8bec09e2009-11-30 15:37:14 -08001152
Doug Zongkereef39442009-04-02 12:14:19 -07001153 -v (--verbose)
1154 Show command lines being executed.
1155
1156 -h (--help)
1157 Display this usage message and exit.
1158"""
1159
1160def Usage(docstring):
Tao Bao89fbb0f2017-01-10 10:47:58 -08001161 print(docstring.rstrip("\n"))
1162 print(COMMON_DOCSTRING)
Doug Zongkereef39442009-04-02 12:14:19 -07001163
1164
1165def ParseOptions(argv,
1166 docstring,
1167 extra_opts="", extra_long_opts=(),
1168 extra_option_handler=None):
1169 """Parse the options in argv and return any arguments that aren't
1170 flags. docstring is the calling module's docstring, to be displayed
1171 for errors and -h. extra_opts and extra_long_opts are for flags
1172 defined by the caller, which are processed by passing them to
1173 extra_option_handler."""
1174
1175 try:
1176 opts, args = getopt.getopt(
Doug Zongker8bec09e2009-11-30 15:37:14 -08001177 argv, "hvp:s:x:" + extra_opts,
Alex Klyubin9667b182015-12-10 13:38:50 -08001178 ["help", "verbose", "path=", "signapk_path=",
1179 "signapk_shared_library_path=", "extra_signapk_args=",
Baligh Uddinbdc2e312014-09-05 17:36:20 -07001180 "java_path=", "java_args=", "public_key_suffix=",
Baligh Uddin601ddea2015-06-09 15:48:14 -07001181 "private_key_suffix=", "boot_signer_path=", "boot_signer_args=",
1182 "verity_signer_path=", "verity_signer_args=", "device_specific=",
Baligh Uddine2048682014-11-20 09:52:05 -08001183 "extra="] +
T.R. Fullhart37e10522013-03-18 10:31:26 -07001184 list(extra_long_opts))
Dan Albert8b72aef2015-03-23 19:13:21 -07001185 except getopt.GetoptError as err:
Doug Zongkereef39442009-04-02 12:14:19 -07001186 Usage(docstring)
Tao Bao89fbb0f2017-01-10 10:47:58 -08001187 print("**", str(err), "**")
Doug Zongkereef39442009-04-02 12:14:19 -07001188 sys.exit(2)
1189
Doug Zongkereef39442009-04-02 12:14:19 -07001190 for o, a in opts:
1191 if o in ("-h", "--help"):
1192 Usage(docstring)
1193 sys.exit()
1194 elif o in ("-v", "--verbose"):
1195 OPTIONS.verbose = True
1196 elif o in ("-p", "--path"):
Doug Zongker602a84e2009-06-18 08:35:12 -07001197 OPTIONS.search_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001198 elif o in ("--signapk_path",):
1199 OPTIONS.signapk_path = a
Alex Klyubin9667b182015-12-10 13:38:50 -08001200 elif o in ("--signapk_shared_library_path",):
1201 OPTIONS.signapk_shared_library_path = a
T.R. Fullhart37e10522013-03-18 10:31:26 -07001202 elif o in ("--extra_signapk_args",):
1203 OPTIONS.extra_signapk_args = shlex.split(a)
1204 elif o in ("--java_path",):
1205 OPTIONS.java_path = a
Baligh Uddin339ee492014-09-05 11:18:07 -07001206 elif o in ("--java_args",):
Tao Baoe95540e2016-11-08 12:08:53 -08001207 OPTIONS.java_args = shlex.split(a)
T.R. Fullhart37e10522013-03-18 10:31:26 -07001208 elif o in ("--public_key_suffix",):
1209 OPTIONS.public_key_suffix = a
1210 elif o in ("--private_key_suffix",):
1211 OPTIONS.private_key_suffix = a
Baligh Uddine2048682014-11-20 09:52:05 -08001212 elif o in ("--boot_signer_path",):
1213 OPTIONS.boot_signer_path = a
Baligh Uddin601ddea2015-06-09 15:48:14 -07001214 elif o in ("--boot_signer_args",):
1215 OPTIONS.boot_signer_args = shlex.split(a)
1216 elif o in ("--verity_signer_path",):
1217 OPTIONS.verity_signer_path = a
1218 elif o in ("--verity_signer_args",):
1219 OPTIONS.verity_signer_args = shlex.split(a)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001220 elif o in ("-s", "--device_specific"):
1221 OPTIONS.device_specific = a
Doug Zongker5ecba702009-12-03 16:36:20 -08001222 elif o in ("-x", "--extra"):
Doug Zongker8bec09e2009-11-30 15:37:14 -08001223 key, value = a.split("=", 1)
1224 OPTIONS.extras[key] = value
Doug Zongkereef39442009-04-02 12:14:19 -07001225 else:
1226 if extra_option_handler is None or not extra_option_handler(o, a):
1227 assert False, "unknown option \"%s\"" % (o,)
1228
Doug Zongker85448772014-09-09 14:59:20 -07001229 if OPTIONS.search_path:
1230 os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
1231 os.pathsep + os.environ["PATH"])
Doug Zongkereef39442009-04-02 12:14:19 -07001232
1233 return args
1234
1235
Tao Bao4c851b12016-09-19 13:54:38 -07001236def MakeTempFile(prefix='tmp', suffix=''):
Doug Zongkerfc44a512014-08-26 13:10:25 -07001237 """Make a temp file and add it to the list of things to be deleted
1238 when Cleanup() is called. Return the filename."""
1239 fd, fn = tempfile.mkstemp(prefix=prefix, suffix=suffix)
1240 os.close(fd)
1241 OPTIONS.tempfiles.append(fn)
1242 return fn
1243
1244
Tao Bao1c830bf2017-12-25 10:43:47 -08001245def MakeTempDir(prefix='tmp', suffix=''):
1246 """Makes a temporary dir that will be cleaned up with a call to Cleanup().
1247
1248 Returns:
1249 The absolute pathname of the new directory.
1250 """
1251 dir_name = tempfile.mkdtemp(suffix=suffix, prefix=prefix)
1252 OPTIONS.tempfiles.append(dir_name)
1253 return dir_name
1254
1255
Doug Zongkereef39442009-04-02 12:14:19 -07001256def Cleanup():
1257 for i in OPTIONS.tempfiles:
1258 if os.path.isdir(i):
Tao Bao1c830bf2017-12-25 10:43:47 -08001259 shutil.rmtree(i, ignore_errors=True)
Doug Zongkereef39442009-04-02 12:14:19 -07001260 else:
1261 os.remove(i)
Tao Bao1c830bf2017-12-25 10:43:47 -08001262 del OPTIONS.tempfiles[:]
Doug Zongker8ce7c252009-05-22 13:34:54 -07001263
1264
1265class PasswordManager(object):
1266 def __init__(self):
Tao Bao76def242017-11-21 09:25:31 -08001267 self.editor = os.getenv("EDITOR")
1268 self.pwfile = os.getenv("ANDROID_PW_FILE")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001269
1270 def GetPasswords(self, items):
1271 """Get passwords corresponding to each string in 'items',
1272 returning a dict. (The dict may have keys in addition to the
1273 values in 'items'.)
1274
1275 Uses the passwords in $ANDROID_PW_FILE if available, letting the
1276 user edit that file to add more needed passwords. If no editor is
1277 available, or $ANDROID_PW_FILE isn't define, prompts the user
1278 interactively in the ordinary way.
1279 """
1280
1281 current = self.ReadFile()
1282
1283 first = True
1284 while True:
1285 missing = []
1286 for i in items:
1287 if i not in current or not current[i]:
1288 missing.append(i)
1289 # Are all the passwords already in the file?
Dan Albert8b72aef2015-03-23 19:13:21 -07001290 if not missing:
1291 return current
Doug Zongker8ce7c252009-05-22 13:34:54 -07001292
1293 for i in missing:
1294 current[i] = ""
1295
1296 if not first:
Tao Bao89fbb0f2017-01-10 10:47:58 -08001297 print("key file %s still missing some passwords." % (self.pwfile,))
Doug Zongker8ce7c252009-05-22 13:34:54 -07001298 answer = raw_input("try to edit again? [y]> ").strip()
1299 if answer and answer[0] not in 'yY':
1300 raise RuntimeError("key passwords unavailable")
1301 first = False
1302
1303 current = self.UpdateAndReadFile(current)
1304
Dan Albert8b72aef2015-03-23 19:13:21 -07001305 def PromptResult(self, current): # pylint: disable=no-self-use
Doug Zongker8ce7c252009-05-22 13:34:54 -07001306 """Prompt the user to enter a value (password) for each key in
1307 'current' whose value is fales. Returns a new dict with all the
1308 values.
1309 """
1310 result = {}
1311 for k, v in sorted(current.iteritems()):
1312 if v:
1313 result[k] = v
1314 else:
1315 while True:
Dan Albert8b72aef2015-03-23 19:13:21 -07001316 result[k] = getpass.getpass(
1317 "Enter password for %s key> " % k).strip()
1318 if result[k]:
1319 break
Doug Zongker8ce7c252009-05-22 13:34:54 -07001320 return result
1321
1322 def UpdateAndReadFile(self, current):
1323 if not self.editor or not self.pwfile:
1324 return self.PromptResult(current)
1325
1326 f = open(self.pwfile, "w")
Dan Albert8b72aef2015-03-23 19:13:21 -07001327 os.chmod(self.pwfile, 0o600)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001328 f.write("# Enter key passwords between the [[[ ]]] brackets.\n")
1329 f.write("# (Additional spaces are harmless.)\n\n")
1330
1331 first_line = None
Dan Albert8b72aef2015-03-23 19:13:21 -07001332 sorted_list = sorted([(not v, k, v) for (k, v) in current.iteritems()])
1333 for i, (_, k, v) in enumerate(sorted_list):
Doug Zongker8ce7c252009-05-22 13:34:54 -07001334 f.write("[[[ %s ]]] %s\n" % (v, k))
1335 if not v and first_line is None:
1336 # position cursor on first line with no password.
1337 first_line = i + 4
1338 f.close()
1339
Tao Bao986ee862018-10-04 15:46:16 -07001340 RunAndCheckOutput([self.editor, "+%d" % (first_line,), self.pwfile])
Doug Zongker8ce7c252009-05-22 13:34:54 -07001341
1342 return self.ReadFile()
1343
1344 def ReadFile(self):
1345 result = {}
Dan Albert8b72aef2015-03-23 19:13:21 -07001346 if self.pwfile is None:
1347 return result
Doug Zongker8ce7c252009-05-22 13:34:54 -07001348 try:
1349 f = open(self.pwfile, "r")
1350 for line in f:
1351 line = line.strip()
Dan Albert8b72aef2015-03-23 19:13:21 -07001352 if not line or line[0] == '#':
1353 continue
Doug Zongker8ce7c252009-05-22 13:34:54 -07001354 m = re.match(r"^\[\[\[\s*(.*?)\s*\]\]\]\s*(\S+)$", line)
1355 if not m:
Tao Bao32fcdab2018-10-12 10:30:39 -07001356 logger.warning("Failed to parse password file: %s", line)
Doug Zongker8ce7c252009-05-22 13:34:54 -07001357 else:
1358 result[m.group(2)] = m.group(1)
1359 f.close()
Dan Albert8b72aef2015-03-23 19:13:21 -07001360 except IOError as e:
Doug Zongker8ce7c252009-05-22 13:34:54 -07001361 if e.errno != errno.ENOENT:
Tao Bao32fcdab2018-10-12 10:30:39 -07001362 logger.exception("Error reading password file:")
Doug Zongker8ce7c252009-05-22 13:34:54 -07001363 return result
Doug Zongker048e7ca2009-06-15 14:31:53 -07001364
1365
Dan Albert8e0178d2015-01-27 15:53:15 -08001366def ZipWrite(zip_file, filename, arcname=None, perms=0o644,
1367 compress_type=None):
1368 import datetime
1369
1370 # http://b/18015246
1371 # Python 2.7's zipfile implementation wrongly thinks that zip64 is required
1372 # for files larger than 2GiB. We can work around this by adjusting their
1373 # limit. Note that `zipfile.writestr()` will not work for strings larger than
1374 # 2GiB. The Python interpreter sometimes rejects strings that large (though
1375 # it isn't clear to me exactly what circumstances cause this).
1376 # `zipfile.write()` must be used directly to work around this.
1377 #
1378 # This mess can be avoided if we port to python3.
1379 saved_zip64_limit = zipfile.ZIP64_LIMIT
1380 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1381
1382 if compress_type is None:
1383 compress_type = zip_file.compression
1384 if arcname is None:
1385 arcname = filename
1386
1387 saved_stat = os.stat(filename)
1388
1389 try:
1390 # `zipfile.write()` doesn't allow us to pass ZipInfo, so just modify the
1391 # file to be zipped and reset it when we're done.
1392 os.chmod(filename, perms)
1393
1394 # Use a fixed timestamp so the output is repeatable.
Bryan Henrye6d547d2018-07-31 18:32:00 -07001395 # Note: Use of fromtimestamp rather than utcfromtimestamp here is
1396 # intentional. zip stores datetimes in local time without a time zone
1397 # attached, so we need "epoch" but in the local time zone to get 2009/01/01
1398 # in the zip archive.
1399 local_epoch = datetime.datetime.fromtimestamp(0)
1400 timestamp = (datetime.datetime(2009, 1, 1) - local_epoch).total_seconds()
Dan Albert8e0178d2015-01-27 15:53:15 -08001401 os.utime(filename, (timestamp, timestamp))
1402
1403 zip_file.write(filename, arcname=arcname, compress_type=compress_type)
1404 finally:
1405 os.chmod(filename, saved_stat.st_mode)
1406 os.utime(filename, (saved_stat.st_atime, saved_stat.st_mtime))
1407 zipfile.ZIP64_LIMIT = saved_zip64_limit
1408
1409
Tao Bao58c1b962015-05-20 09:32:18 -07001410def ZipWriteStr(zip_file, zinfo_or_arcname, data, perms=None,
Tao Baof3282b42015-04-01 11:21:55 -07001411 compress_type=None):
1412 """Wrap zipfile.writestr() function to work around the zip64 limit.
1413
1414 Even with the ZIP64_LIMIT workaround, it won't allow writing a string
1415 longer than 2GiB. It gives 'OverflowError: size does not fit in an int'
1416 when calling crc32(bytes).
1417
1418 But it still works fine to write a shorter string into a large zip file.
1419 We should use ZipWrite() whenever possible, and only use ZipWriteStr()
1420 when we know the string won't be too long.
1421 """
1422
1423 saved_zip64_limit = zipfile.ZIP64_LIMIT
1424 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1425
1426 if not isinstance(zinfo_or_arcname, zipfile.ZipInfo):
1427 zinfo = zipfile.ZipInfo(filename=zinfo_or_arcname)
Dan Albert8b72aef2015-03-23 19:13:21 -07001428 zinfo.compress_type = zip_file.compression
Tao Bao58c1b962015-05-20 09:32:18 -07001429 if perms is None:
Tao Bao2a410582015-07-10 17:18:23 -07001430 perms = 0o100644
Geremy Condra36bd3652014-02-06 19:45:10 -08001431 else:
Tao Baof3282b42015-04-01 11:21:55 -07001432 zinfo = zinfo_or_arcname
1433
1434 # If compress_type is given, it overrides the value in zinfo.
1435 if compress_type is not None:
1436 zinfo.compress_type = compress_type
1437
Tao Bao58c1b962015-05-20 09:32:18 -07001438 # If perms is given, it has a priority.
1439 if perms is not None:
Tao Bao2a410582015-07-10 17:18:23 -07001440 # If perms doesn't set the file type, mark it as a regular file.
1441 if perms & 0o770000 == 0:
1442 perms |= 0o100000
Tao Bao58c1b962015-05-20 09:32:18 -07001443 zinfo.external_attr = perms << 16
1444
Tao Baof3282b42015-04-01 11:21:55 -07001445 # Use a fixed timestamp so the output is repeatable.
Tao Baof3282b42015-04-01 11:21:55 -07001446 zinfo.date_time = (2009, 1, 1, 0, 0, 0)
1447
Dan Albert8b72aef2015-03-23 19:13:21 -07001448 zip_file.writestr(zinfo, data)
Tao Baof3282b42015-04-01 11:21:55 -07001449 zipfile.ZIP64_LIMIT = saved_zip64_limit
1450
1451
Tao Bao89d7ab22017-12-14 17:05:33 -08001452def ZipDelete(zip_filename, entries):
1453 """Deletes entries from a ZIP file.
1454
1455 Since deleting entries from a ZIP file is not supported, it shells out to
1456 'zip -d'.
1457
1458 Args:
1459 zip_filename: The name of the ZIP file.
1460 entries: The name of the entry, or the list of names to be deleted.
1461
1462 Raises:
1463 AssertionError: In case of non-zero return from 'zip'.
1464 """
1465 if isinstance(entries, basestring):
1466 entries = [entries]
1467 cmd = ["zip", "-d", zip_filename] + entries
Tao Bao986ee862018-10-04 15:46:16 -07001468 RunAndCheckOutput(cmd)
Tao Bao89d7ab22017-12-14 17:05:33 -08001469
1470
Tao Baof3282b42015-04-01 11:21:55 -07001471def ZipClose(zip_file):
1472 # http://b/18015246
1473 # zipfile also refers to ZIP64_LIMIT during close() when it writes out the
1474 # central directory.
1475 saved_zip64_limit = zipfile.ZIP64_LIMIT
1476 zipfile.ZIP64_LIMIT = (1 << 32) - 1
1477
1478 zip_file.close()
1479
1480 zipfile.ZIP64_LIMIT = saved_zip64_limit
Doug Zongker05d3dea2009-06-22 11:32:31 -07001481
1482
1483class DeviceSpecificParams(object):
1484 module = None
1485 def __init__(self, **kwargs):
1486 """Keyword arguments to the constructor become attributes of this
1487 object, which is passed to all functions in the device-specific
1488 module."""
1489 for k, v in kwargs.iteritems():
1490 setattr(self, k, v)
Doug Zongker8bec09e2009-11-30 15:37:14 -08001491 self.extras = OPTIONS.extras
Doug Zongker05d3dea2009-06-22 11:32:31 -07001492
1493 if self.module is None:
1494 path = OPTIONS.device_specific
Dan Albert8b72aef2015-03-23 19:13:21 -07001495 if not path:
1496 return
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001497 try:
1498 if os.path.isdir(path):
1499 info = imp.find_module("releasetools", [path])
1500 else:
1501 d, f = os.path.split(path)
1502 b, x = os.path.splitext(f)
1503 if x == ".py":
1504 f = b
1505 info = imp.find_module(f, [d])
Tao Bao32fcdab2018-10-12 10:30:39 -07001506 logger.info("loaded device-specific extensions from %s", path)
Doug Zongker8e2f2b92009-06-24 14:34:57 -07001507 self.module = imp.load_module("device_specific", *info)
1508 except ImportError:
Tao Bao32fcdab2018-10-12 10:30:39 -07001509 logger.info("unable to load device-specific module; assuming none")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001510
1511 def _DoCall(self, function_name, *args, **kwargs):
1512 """Call the named function in the device-specific module, passing
1513 the given args and kwargs. The first argument to the call will be
1514 the DeviceSpecific object itself. If there is no module, or the
1515 module does not define the function, return the value of the
1516 'default' kwarg (which itself defaults to None)."""
1517 if self.module is None or not hasattr(self.module, function_name):
Tao Bao76def242017-11-21 09:25:31 -08001518 return kwargs.get("default")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001519 return getattr(self.module, function_name)(*((self,) + args), **kwargs)
1520
1521 def FullOTA_Assertions(self):
1522 """Called after emitting the block of assertions at the top of a
1523 full OTA package. Implementations can add whatever additional
1524 assertions they like."""
1525 return self._DoCall("FullOTA_Assertions")
1526
Doug Zongkere5ff5902012-01-17 10:55:37 -08001527 def FullOTA_InstallBegin(self):
1528 """Called at the start of full OTA installation."""
1529 return self._DoCall("FullOTA_InstallBegin")
1530
Doug Zongker05d3dea2009-06-22 11:32:31 -07001531 def FullOTA_InstallEnd(self):
1532 """Called at the end of full OTA installation; typically this is
1533 used to install the image for the device's baseband processor."""
1534 return self._DoCall("FullOTA_InstallEnd")
1535
1536 def IncrementalOTA_Assertions(self):
1537 """Called after emitting the block of assertions at the top of an
1538 incremental OTA package. Implementations can add whatever
1539 additional assertions they like."""
1540 return self._DoCall("IncrementalOTA_Assertions")
1541
Doug Zongkere5ff5902012-01-17 10:55:37 -08001542 def IncrementalOTA_VerifyBegin(self):
1543 """Called at the start of the verification phase of incremental
1544 OTA installation; additional checks can be placed here to abort
1545 the script before any changes are made."""
1546 return self._DoCall("IncrementalOTA_VerifyBegin")
1547
Doug Zongker05d3dea2009-06-22 11:32:31 -07001548 def IncrementalOTA_VerifyEnd(self):
1549 """Called at the end of the verification phase of incremental OTA
1550 installation; additional checks can be placed here to abort the
1551 script before any changes are made."""
1552 return self._DoCall("IncrementalOTA_VerifyEnd")
1553
Doug Zongkere5ff5902012-01-17 10:55:37 -08001554 def IncrementalOTA_InstallBegin(self):
1555 """Called at the start of incremental OTA installation (after
1556 verification is complete)."""
1557 return self._DoCall("IncrementalOTA_InstallBegin")
1558
Doug Zongker05d3dea2009-06-22 11:32:31 -07001559 def IncrementalOTA_InstallEnd(self):
1560 """Called at the end of incremental OTA installation; typically
1561 this is used to install the image for the device's baseband
1562 processor."""
1563 return self._DoCall("IncrementalOTA_InstallEnd")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001564
Tao Bao9bc6bb22015-11-09 16:58:28 -08001565 def VerifyOTA_Assertions(self):
1566 return self._DoCall("VerifyOTA_Assertions")
1567
Tao Bao76def242017-11-21 09:25:31 -08001568
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001569class File(object):
Tao Bao76def242017-11-21 09:25:31 -08001570 def __init__(self, name, data, compress_size=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001571 self.name = name
1572 self.data = data
1573 self.size = len(data)
YOUNG HO CHAccc5c402016-10-13 13:40:46 +09001574 self.compress_size = compress_size or self.size
Doug Zongker55d93282011-01-25 17:03:34 -08001575 self.sha1 = sha1(data).hexdigest()
1576
1577 @classmethod
1578 def FromLocalFile(cls, name, diskname):
1579 f = open(diskname, "rb")
1580 data = f.read()
1581 f.close()
1582 return File(name, data)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001583
1584 def WriteToTemp(self):
1585 t = tempfile.NamedTemporaryFile()
1586 t.write(self.data)
1587 t.flush()
1588 return t
1589
Dan Willemsen2ee00d52017-03-05 19:51:56 -08001590 def WriteToDir(self, d):
1591 with open(os.path.join(d, self.name), "wb") as fp:
1592 fp.write(self.data)
1593
Geremy Condra36bd3652014-02-06 19:45:10 -08001594 def AddToZip(self, z, compression=None):
Tao Baof3282b42015-04-01 11:21:55 -07001595 ZipWriteStr(z, self.name, self.data, compress_type=compression)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001596
Tao Bao76def242017-11-21 09:25:31 -08001597
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001598DIFF_PROGRAM_BY_EXT = {
1599 ".gz" : "imgdiff",
1600 ".zip" : ["imgdiff", "-z"],
1601 ".jar" : ["imgdiff", "-z"],
1602 ".apk" : ["imgdiff", "-z"],
1603 ".img" : "imgdiff",
1604 }
1605
Tao Bao76def242017-11-21 09:25:31 -08001606
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001607class Difference(object):
Doug Zongker24cd2802012-08-14 16:36:15 -07001608 def __init__(self, tf, sf, diff_program=None):
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001609 self.tf = tf
1610 self.sf = sf
1611 self.patch = None
Doug Zongker24cd2802012-08-14 16:36:15 -07001612 self.diff_program = diff_program
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001613
1614 def ComputePatch(self):
1615 """Compute the patch (as a string of data) needed to turn sf into
1616 tf. Returns the same tuple as GetPatch()."""
1617
1618 tf = self.tf
1619 sf = self.sf
1620
Doug Zongker24cd2802012-08-14 16:36:15 -07001621 if self.diff_program:
1622 diff_program = self.diff_program
1623 else:
1624 ext = os.path.splitext(tf.name)[1]
1625 diff_program = DIFF_PROGRAM_BY_EXT.get(ext, "bsdiff")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001626
1627 ttemp = tf.WriteToTemp()
1628 stemp = sf.WriteToTemp()
1629
1630 ext = os.path.splitext(tf.name)[1]
1631
1632 try:
1633 ptemp = tempfile.NamedTemporaryFile()
1634 if isinstance(diff_program, list):
1635 cmd = copy.copy(diff_program)
1636 else:
1637 cmd = [diff_program]
1638 cmd.append(stemp.name)
1639 cmd.append(ttemp.name)
1640 cmd.append(ptemp.name)
1641 p = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Doug Zongkerf8340082014-08-05 10:39:37 -07001642 err = []
1643 def run():
1644 _, e = p.communicate()
Dan Albert8b72aef2015-03-23 19:13:21 -07001645 if e:
1646 err.append(e)
Doug Zongkerf8340082014-08-05 10:39:37 -07001647 th = threading.Thread(target=run)
1648 th.start()
1649 th.join(timeout=300) # 5 mins
1650 if th.is_alive():
Tao Bao32fcdab2018-10-12 10:30:39 -07001651 logger.warning("diff command timed out")
Doug Zongkerf8340082014-08-05 10:39:37 -07001652 p.terminate()
1653 th.join(5)
1654 if th.is_alive():
1655 p.kill()
1656 th.join()
1657
Tianjie Xua2a9f992018-01-05 15:15:54 -08001658 if p.returncode != 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001659 logger.warning("Failure running %s:\n%s\n", diff_program, "".join(err))
Doug Zongkerf8340082014-08-05 10:39:37 -07001660 self.patch = None
1661 return None, None, None
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001662 diff = ptemp.read()
1663 finally:
1664 ptemp.close()
1665 stemp.close()
1666 ttemp.close()
1667
1668 self.patch = diff
1669 return self.tf, self.sf, self.patch
1670
1671
1672 def GetPatch(self):
Tao Bao76def242017-11-21 09:25:31 -08001673 """Returns a tuple of (target_file, source_file, patch_data).
1674
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001675 patch_data may be None if ComputePatch hasn't been called, or if
Tao Bao76def242017-11-21 09:25:31 -08001676 computing the patch failed.
1677 """
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001678 return self.tf, self.sf, self.patch
1679
1680
1681def ComputeDifferences(diffs):
1682 """Call ComputePatch on all the Difference objects in 'diffs'."""
Tao Bao32fcdab2018-10-12 10:30:39 -07001683 logger.info("%d diffs to compute", len(diffs))
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001684
1685 # Do the largest files first, to try and reduce the long-pole effect.
1686 by_size = [(i.tf.size, i) for i in diffs]
1687 by_size.sort(reverse=True)
1688 by_size = [i[1] for i in by_size]
1689
1690 lock = threading.Lock()
1691 diff_iter = iter(by_size) # accessed under lock
1692
1693 def worker():
1694 try:
1695 lock.acquire()
1696 for d in diff_iter:
1697 lock.release()
1698 start = time.time()
1699 d.ComputePatch()
1700 dur = time.time() - start
1701 lock.acquire()
1702
1703 tf, sf, patch = d.GetPatch()
1704 if sf.name == tf.name:
1705 name = tf.name
1706 else:
1707 name = "%s (%s)" % (tf.name, sf.name)
1708 if patch is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07001709 logger.error("patching failed! %40s", name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001710 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001711 logger.info(
1712 "%8.2f sec %8d / %8d bytes (%6.2f%%) %s", dur, len(patch),
1713 tf.size, 100.0 * len(patch) / tf.size, name)
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001714 lock.release()
Tao Bao32fcdab2018-10-12 10:30:39 -07001715 except Exception:
1716 logger.exception("Failed to compute diff from worker")
Doug Zongkerea5d7a92010-09-12 15:26:16 -07001717 raise
1718
1719 # start worker threads; wait for them all to finish.
1720 threads = [threading.Thread(target=worker)
1721 for i in range(OPTIONS.worker_threads)]
1722 for th in threads:
1723 th.start()
1724 while threads:
1725 threads.pop().join()
Doug Zongker96a57e72010-09-26 14:57:41 -07001726
1727
Dan Albert8b72aef2015-03-23 19:13:21 -07001728class BlockDifference(object):
1729 def __init__(self, partition, tgt, src=None, check_first_block=False,
Tao Bao293fd132016-06-11 12:19:23 -07001730 version=None, disable_imgdiff=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001731 self.tgt = tgt
1732 self.src = src
1733 self.partition = partition
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001734 self.check_first_block = check_first_block
Tao Bao293fd132016-06-11 12:19:23 -07001735 self.disable_imgdiff = disable_imgdiff
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001736
Tao Baodd2a5892015-03-12 12:32:37 -07001737 if version is None:
Tao Bao0582cb62017-12-21 11:47:01 -08001738 version = max(
1739 int(i) for i in
1740 OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))
Tao Bao8fad03e2017-03-01 14:36:26 -08001741 assert version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001742 self.version = version
Doug Zongker62338182014-09-08 08:29:55 -07001743
1744 b = blockimgdiff.BlockImageDiff(tgt, src, threads=OPTIONS.worker_threads,
Tao Bao293fd132016-06-11 12:19:23 -07001745 version=self.version,
1746 disable_imgdiff=self.disable_imgdiff)
Tao Bao04bce3a2018-02-28 11:11:00 -08001747 self.path = os.path.join(MakeTempDir(), partition)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001748 b.Compute(self.path)
Tao Baod8d14be2016-02-04 14:26:02 -08001749 self._required_cache = b.max_stashed_size
Tao Baod522bdc2016-04-12 15:53:16 -07001750 self.touched_src_ranges = b.touched_src_ranges
1751 self.touched_src_sha1 = b.touched_src_sha1
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001752
Tao Baoaac4ad52015-10-16 15:26:34 -07001753 if src is None:
1754 _, self.device = GetTypeAndDevice("/" + partition, OPTIONS.info_dict)
1755 else:
1756 _, self.device = GetTypeAndDevice("/" + partition,
1757 OPTIONS.source_info_dict)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001758
Tao Baod8d14be2016-02-04 14:26:02 -08001759 @property
1760 def required_cache(self):
1761 return self._required_cache
1762
Tao Bao76def242017-11-21 09:25:31 -08001763 def WriteScript(self, script, output_zip, progress=None,
1764 write_verify_script=False):
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001765 if not self.src:
1766 # write the output unconditionally
Jesse Zhao75bcea02015-01-06 10:59:53 -08001767 script.Print("Patching %s image unconditionally..." % (self.partition,))
1768 else:
1769 script.Print("Patching %s image after verification." % (self.partition,))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001770
Dan Albert8b72aef2015-03-23 19:13:21 -07001771 if progress:
1772 script.ShowProgress(progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001773 self._WriteUpdate(script, output_zip)
Tao Bao76def242017-11-21 09:25:31 -08001774
1775 if write_verify_script:
Tianjie Xub2deb222016-03-25 15:01:33 -07001776 self._WritePostInstallVerifyScript(script)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001777
Tao Bao9bc6bb22015-11-09 16:58:28 -08001778 def WriteStrictVerifyScript(self, script):
1779 """Verify all the blocks in the care_map, including clobbered blocks.
1780
1781 This differs from the WriteVerifyScript() function: a) it prints different
1782 error messages; b) it doesn't allow half-way updated images to pass the
1783 verification."""
1784
1785 partition = self.partition
1786 script.Print("Verifying %s..." % (partition,))
1787 ranges = self.tgt.care_map
1788 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001789 script.AppendExtra(
1790 'range_sha1("%s", "%s") == "%s" && ui_print(" Verified.") || '
1791 'ui_print("\\"%s\\" has unexpected contents.");' % (
1792 self.device, ranges_str,
1793 self.tgt.TotalSha1(include_clobbered_blocks=True),
1794 self.device))
Tao Bao9bc6bb22015-11-09 16:58:28 -08001795 script.AppendExtra("")
1796
Tao Baod522bdc2016-04-12 15:53:16 -07001797 def WriteVerifyScript(self, script, touched_blocks_only=False):
Sami Tolvanendd67a292014-12-09 16:40:34 +00001798 partition = self.partition
Tao Baof9efe282016-04-14 15:58:05 -07001799
1800 # full OTA
Jesse Zhao75bcea02015-01-06 10:59:53 -08001801 if not self.src:
Sami Tolvanendd67a292014-12-09 16:40:34 +00001802 script.Print("Image %s will be patched unconditionally." % (partition,))
Tao Baof9efe282016-04-14 15:58:05 -07001803
1804 # incremental OTA
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001805 else:
Tao Bao8fad03e2017-03-01 14:36:26 -08001806 if touched_blocks_only:
Tao Baod522bdc2016-04-12 15:53:16 -07001807 ranges = self.touched_src_ranges
1808 expected_sha1 = self.touched_src_sha1
1809 else:
1810 ranges = self.src.care_map.subtract(self.src.clobbered_blocks)
1811 expected_sha1 = self.src.TotalSha1()
Tao Baof9efe282016-04-14 15:58:05 -07001812
1813 # No blocks to be checked, skipping.
1814 if not ranges:
1815 return
1816
Tao Bao5ece99d2015-05-12 11:42:31 -07001817 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001818 script.AppendExtra(
1819 'if (range_sha1("%s", "%s") == "%s" || block_image_verify("%s", '
1820 'package_extract_file("%s.transfer.list"), "%s.new.dat", '
1821 '"%s.patch.dat")) then' % (
1822 self.device, ranges_str, expected_sha1,
1823 self.device, partition, partition, partition))
Tao Baodd2a5892015-03-12 12:32:37 -07001824 script.Print('Verified %s image...' % (partition,))
Dan Albert8b72aef2015-03-23 19:13:21 -07001825 script.AppendExtra('else')
Sami Tolvanendd67a292014-12-09 16:40:34 +00001826
Tianjie Xufc3422a2015-12-15 11:53:59 -08001827 if self.version >= 4:
1828
1829 # Bug: 21124327
1830 # When generating incrementals for the system and vendor partitions in
1831 # version 4 or newer, explicitly check the first block (which contains
1832 # the superblock) of the partition to see if it's what we expect. If
1833 # this check fails, give an explicit log message about the partition
1834 # having been remounted R/W (the most likely explanation).
1835 if self.check_first_block:
1836 script.AppendExtra('check_first_block("%s");' % (self.device,))
1837
1838 # If version >= 4, try block recovery before abort update
Tianjie Xu209db462016-05-24 17:34:52 -07001839 if partition == "system":
1840 code = ErrorCode.SYSTEM_RECOVER_FAILURE
1841 else:
1842 code = ErrorCode.VENDOR_RECOVER_FAILURE
Tianjie Xufc3422a2015-12-15 11:53:59 -08001843 script.AppendExtra((
1844 'ifelse (block_image_recover("{device}", "{ranges}") && '
1845 'block_image_verify("{device}", '
1846 'package_extract_file("{partition}.transfer.list"), '
1847 '"{partition}.new.dat", "{partition}.patch.dat"), '
1848 'ui_print("{partition} recovered successfully."), '
Tianjie Xu209db462016-05-24 17:34:52 -07001849 'abort("E{code}: {partition} partition fails to recover"));\n'
Tianjie Xufc3422a2015-12-15 11:53:59 -08001850 'endif;').format(device=self.device, ranges=ranges_str,
Tianjie Xu209db462016-05-24 17:34:52 -07001851 partition=partition, code=code))
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001852
Tao Baodd2a5892015-03-12 12:32:37 -07001853 # Abort the OTA update. Note that the incremental OTA cannot be applied
1854 # even if it may match the checksum of the target partition.
1855 # a) If version < 3, operations like move and erase will make changes
1856 # unconditionally and damage the partition.
1857 # b) If version >= 3, it won't even reach here.
Tianjie Xufc3422a2015-12-15 11:53:59 -08001858 else:
Tianjie Xu209db462016-05-24 17:34:52 -07001859 if partition == "system":
1860 code = ErrorCode.SYSTEM_VERIFICATION_FAILURE
1861 else:
1862 code = ErrorCode.VENDOR_VERIFICATION_FAILURE
1863 script.AppendExtra((
1864 'abort("E%d: %s partition has unexpected contents");\n'
1865 'endif;') % (code, partition))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001866
Tao Bao5fcaaef2015-06-01 13:40:49 -07001867 def _WritePostInstallVerifyScript(self, script):
1868 partition = self.partition
1869 script.Print('Verifying the updated %s image...' % (partition,))
1870 # Unlike pre-install verification, clobbered_blocks should not be ignored.
1871 ranges = self.tgt.care_map
1872 ranges_str = ranges.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001873 script.AppendExtra(
1874 'if range_sha1("%s", "%s") == "%s" then' % (
1875 self.device, ranges_str,
1876 self.tgt.TotalSha1(include_clobbered_blocks=True)))
Tao Baoe9b61912015-07-09 17:37:49 -07001877
1878 # Bug: 20881595
1879 # Verify that extended blocks are really zeroed out.
1880 if self.tgt.extended:
1881 ranges_str = self.tgt.extended.to_string_raw()
Tao Bao76def242017-11-21 09:25:31 -08001882 script.AppendExtra(
1883 'if range_sha1("%s", "%s") == "%s" then' % (
1884 self.device, ranges_str,
1885 self._HashZeroBlocks(self.tgt.extended.size())))
Tao Baoe9b61912015-07-09 17:37:49 -07001886 script.Print('Verified the updated %s image.' % (partition,))
Tianjie Xu209db462016-05-24 17:34:52 -07001887 if partition == "system":
1888 code = ErrorCode.SYSTEM_NONZERO_CONTENTS
1889 else:
1890 code = ErrorCode.VENDOR_NONZERO_CONTENTS
Tao Baoe9b61912015-07-09 17:37:49 -07001891 script.AppendExtra(
1892 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001893 ' abort("E%d: %s partition has unexpected non-zero contents after '
1894 'OTA update");\n'
1895 'endif;' % (code, partition))
Tao Baoe9b61912015-07-09 17:37:49 -07001896 else:
1897 script.Print('Verified the updated %s image.' % (partition,))
1898
Tianjie Xu209db462016-05-24 17:34:52 -07001899 if partition == "system":
1900 code = ErrorCode.SYSTEM_UNEXPECTED_CONTENTS
1901 else:
1902 code = ErrorCode.VENDOR_UNEXPECTED_CONTENTS
1903
Tao Bao5fcaaef2015-06-01 13:40:49 -07001904 script.AppendExtra(
1905 'else\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001906 ' abort("E%d: %s partition has unexpected contents after OTA '
1907 'update");\n'
1908 'endif;' % (code, partition))
Tao Bao5fcaaef2015-06-01 13:40:49 -07001909
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001910 def _WriteUpdate(self, script, output_zip):
Dan Albert8e0178d2015-01-27 15:53:15 -08001911 ZipWrite(output_zip,
1912 '{}.transfer.list'.format(self.path),
1913 '{}.transfer.list'.format(self.partition))
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001914
Tao Bao76def242017-11-21 09:25:31 -08001915 # For full OTA, compress the new.dat with brotli with quality 6 to reduce
1916 # its size. Quailty 9 almost triples the compression time but doesn't
1917 # further reduce the size too much. For a typical 1.8G system.new.dat
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001918 # zip | brotli(quality 6) | brotli(quality 9)
1919 # compressed_size: 942M | 869M (~8% reduced) | 854M
1920 # compression_time: 75s | 265s | 719s
1921 # decompression_time: 15s | 25s | 25s
1922
1923 if not self.src:
Alex Deymob10e07a2017-11-09 23:53:42 +01001924 brotli_cmd = ['brotli', '--quality=6',
1925 '--output={}.new.dat.br'.format(self.path),
1926 '{}.new.dat'.format(self.path)]
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001927 print("Compressing {}.new.dat with brotli".format(self.partition))
Tao Bao986ee862018-10-04 15:46:16 -07001928 RunAndCheckOutput(brotli_cmd)
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001929
1930 new_data_name = '{}.new.dat.br'.format(self.partition)
1931 ZipWrite(output_zip,
1932 '{}.new.dat.br'.format(self.path),
1933 new_data_name,
1934 compress_type=zipfile.ZIP_STORED)
1935 else:
1936 new_data_name = '{}.new.dat'.format(self.partition)
1937 ZipWrite(output_zip, '{}.new.dat'.format(self.path), new_data_name)
1938
Dan Albert8e0178d2015-01-27 15:53:15 -08001939 ZipWrite(output_zip,
1940 '{}.patch.dat'.format(self.path),
1941 '{}.patch.dat'.format(self.partition),
1942 compress_type=zipfile.ZIP_STORED)
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001943
Tianjie Xu209db462016-05-24 17:34:52 -07001944 if self.partition == "system":
1945 code = ErrorCode.SYSTEM_UPDATE_FAILURE
1946 else:
1947 code = ErrorCode.VENDOR_UPDATE_FAILURE
1948
Dan Albert8e0178d2015-01-27 15:53:15 -08001949 call = ('block_image_update("{device}", '
1950 'package_extract_file("{partition}.transfer.list"), '
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001951 '"{new_data_name}", "{partition}.patch.dat") ||\n'
Tianjie Xu209db462016-05-24 17:34:52 -07001952 ' abort("E{code}: Failed to update {partition} image.");'.format(
Tianjie Xub0a29ad2017-07-06 15:13:59 -07001953 device=self.device, partition=self.partition,
1954 new_data_name=new_data_name, code=code))
Dan Albert8b72aef2015-03-23 19:13:21 -07001955 script.AppendExtra(script.WordWrap(call))
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001956
Dan Albert8b72aef2015-03-23 19:13:21 -07001957 def _HashBlocks(self, source, ranges): # pylint: disable=no-self-use
Sami Tolvanendd67a292014-12-09 16:40:34 +00001958 data = source.ReadRangeSet(ranges)
1959 ctx = sha1()
1960
1961 for p in data:
1962 ctx.update(p)
1963
1964 return ctx.hexdigest()
1965
Tao Baoe9b61912015-07-09 17:37:49 -07001966 def _HashZeroBlocks(self, num_blocks): # pylint: disable=no-self-use
1967 """Return the hash value for all zero blocks."""
1968 zero_block = '\x00' * 4096
1969 ctx = sha1()
1970 for _ in range(num_blocks):
1971 ctx.update(zero_block)
1972
1973 return ctx.hexdigest()
1974
Doug Zongkerab7ca1d2014-08-26 10:40:28 -07001975
1976DataImage = blockimgdiff.DataImage
1977
Tao Bao76def242017-11-21 09:25:31 -08001978
Doug Zongker96a57e72010-09-26 14:57:41 -07001979# map recovery.fstab's fs_types to mount/format "partition types"
Dan Albert8b72aef2015-03-23 19:13:21 -07001980PARTITION_TYPES = {
Dan Albert8b72aef2015-03-23 19:13:21 -07001981 "ext4": "EMMC",
1982 "emmc": "EMMC",
Mohamad Ayyash95e74c12015-05-01 15:39:36 -07001983 "f2fs": "EMMC",
1984 "squashfs": "EMMC"
Dan Albert8b72aef2015-03-23 19:13:21 -07001985}
Doug Zongker96a57e72010-09-26 14:57:41 -07001986
Tao Bao76def242017-11-21 09:25:31 -08001987
Doug Zongker96a57e72010-09-26 14:57:41 -07001988def GetTypeAndDevice(mount_point, info):
1989 fstab = info["fstab"]
1990 if fstab:
Dan Albert8b72aef2015-03-23 19:13:21 -07001991 return (PARTITION_TYPES[fstab[mount_point].fs_type],
1992 fstab[mount_point].device)
Doug Zongker96a57e72010-09-26 14:57:41 -07001993 else:
Dan Albert8b72aef2015-03-23 19:13:21 -07001994 raise KeyError
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00001995
1996
1997def ParseCertificate(data):
Tao Bao17e4e612018-02-16 17:12:54 -08001998 """Parses and converts a PEM-encoded certificate into DER-encoded.
1999
2000 This gives the same result as `openssl x509 -in <filename> -outform DER`.
2001
2002 Returns:
2003 The decoded certificate string.
2004 """
2005 cert_buffer = []
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002006 save = False
2007 for line in data.split("\n"):
2008 if "--END CERTIFICATE--" in line:
2009 break
2010 if save:
Tao Bao17e4e612018-02-16 17:12:54 -08002011 cert_buffer.append(line)
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002012 if "--BEGIN CERTIFICATE--" in line:
2013 save = True
Tao Bao17e4e612018-02-16 17:12:54 -08002014 cert = "".join(cert_buffer).decode('base64')
Baligh Uddinbeb6afd2013-11-13 00:22:34 +00002015 return cert
Doug Zongkerc9253822014-02-04 12:17:58 -08002016
Tao Bao04e1f012018-02-04 12:13:35 -08002017
2018def ExtractPublicKey(cert):
2019 """Extracts the public key (PEM-encoded) from the given certificate file.
2020
2021 Args:
2022 cert: The certificate filename.
2023
2024 Returns:
2025 The public key string.
2026
2027 Raises:
2028 AssertionError: On non-zero return from 'openssl'.
2029 """
2030 # The behavior with '-out' is different between openssl 1.1 and openssl 1.0.
2031 # While openssl 1.1 writes the key into the given filename followed by '-out',
2032 # openssl 1.0 (both of 1.0.1 and 1.0.2) doesn't. So we collect the output from
2033 # stdout instead.
2034 cmd = ['openssl', 'x509', '-pubkey', '-noout', '-in', cert]
2035 proc = Run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2036 pubkey, stderrdata = proc.communicate()
2037 assert proc.returncode == 0, \
2038 'Failed to dump public key from certificate: %s\n%s' % (cert, stderrdata)
2039 return pubkey
2040
2041
Doug Zongker412c02f2014-02-13 10:58:24 -08002042def MakeRecoveryPatch(input_dir, output_sink, recovery_img, boot_img,
2043 info_dict=None):
Tao Bao6d5d6232018-03-09 17:04:42 -08002044 """Generates the recovery-from-boot patch and writes the script to output.
Doug Zongkerc9253822014-02-04 12:17:58 -08002045
Tao Bao6d5d6232018-03-09 17:04:42 -08002046 Most of the space in the boot and recovery images is just the kernel, which is
2047 identical for the two, so the resulting patch should be efficient. Add it to
2048 the output zip, along with a shell script that is run from init.rc on first
2049 boot to actually do the patching and install the new recovery image.
2050
2051 Args:
2052 input_dir: The top-level input directory of the target-files.zip.
2053 output_sink: The callback function that writes the result.
2054 recovery_img: File object for the recovery image.
2055 boot_img: File objects for the boot image.
2056 info_dict: A dict returned by common.LoadInfoDict() on the input
2057 target_files. Will use OPTIONS.info_dict if None has been given.
Doug Zongkerc9253822014-02-04 12:17:58 -08002058 """
Doug Zongker412c02f2014-02-13 10:58:24 -08002059 if info_dict is None:
2060 info_dict = OPTIONS.info_dict
2061
Tao Bao6d5d6232018-03-09 17:04:42 -08002062 full_recovery_image = info_dict.get("full_recovery_image") == "true"
Doug Zongkerc9253822014-02-04 12:17:58 -08002063
Tao Baof2cffbd2015-07-22 12:33:18 -07002064 if full_recovery_image:
2065 output_sink("etc/recovery.img", recovery_img.data)
2066
2067 else:
Tao Bao6d5d6232018-03-09 17:04:42 -08002068 system_root_image = info_dict.get("system_root_image") == "true"
Tao Baof2cffbd2015-07-22 12:33:18 -07002069 path = os.path.join(input_dir, "SYSTEM", "etc", "recovery-resource.dat")
Tao Bao6d5d6232018-03-09 17:04:42 -08002070 # With system-root-image, boot and recovery images will have mismatching
2071 # entries (only recovery has the ramdisk entry) (Bug: 72731506). Use bsdiff
2072 # to handle such a case.
2073 if system_root_image:
2074 diff_program = ["bsdiff"]
Tao Baof2cffbd2015-07-22 12:33:18 -07002075 bonus_args = ""
Tao Bao6d5d6232018-03-09 17:04:42 -08002076 assert not os.path.exists(path)
2077 else:
2078 diff_program = ["imgdiff"]
2079 if os.path.exists(path):
2080 diff_program.append("-b")
2081 diff_program.append(path)
Tao Bao4948aed2018-07-13 16:11:16 -07002082 bonus_args = "--bonus /system/etc/recovery-resource.dat"
Tao Bao6d5d6232018-03-09 17:04:42 -08002083 else:
2084 bonus_args = ""
Tao Baof2cffbd2015-07-22 12:33:18 -07002085
2086 d = Difference(recovery_img, boot_img, diff_program=diff_program)
2087 _, _, patch = d.ComputePatch()
2088 output_sink("recovery-from-boot.p", patch)
Doug Zongkerc9253822014-02-04 12:17:58 -08002089
Dan Albertebb19aa2015-03-27 19:11:53 -07002090 try:
Tao Bao6f0b2192015-10-13 16:37:12 -07002091 # The following GetTypeAndDevice()s need to use the path in the target
2092 # info_dict instead of source_info_dict.
Dan Albertebb19aa2015-03-27 19:11:53 -07002093 boot_type, boot_device = GetTypeAndDevice("/boot", info_dict)
2094 recovery_type, recovery_device = GetTypeAndDevice("/recovery", info_dict)
2095 except KeyError:
Ying Wanga961a092014-07-29 11:42:37 -07002096 return
Doug Zongkerc9253822014-02-04 12:17:58 -08002097
Tao Baof2cffbd2015-07-22 12:33:18 -07002098 if full_recovery_image:
2099 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002100if ! applypatch --check %(type)s:%(device)s:%(size)d:%(sha1)s; then
2101 applypatch \\
2102 --flash /system/etc/recovery.img \\
2103 --target %(type)s:%(device)s:%(size)d:%(sha1)s && \\
2104 log -t recovery "Installing new recovery image: succeeded" || \\
2105 log -t recovery "Installing new recovery image: failed"
Tao Baof2cffbd2015-07-22 12:33:18 -07002106else
2107 log -t recovery "Recovery image already installed"
2108fi
2109""" % {'type': recovery_type,
2110 'device': recovery_device,
2111 'sha1': recovery_img.sha1,
2112 'size': recovery_img.size}
2113 else:
2114 sh = """#!/system/bin/sh
Tao Bao4948aed2018-07-13 16:11:16 -07002115if ! applypatch --check %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then
2116 applypatch %(bonus_args)s \\
2117 --patch /system/recovery-from-boot.p \\
2118 --source %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s \\
2119 --target %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s && \\
2120 log -t recovery "Installing new recovery image: succeeded" || \\
2121 log -t recovery "Installing new recovery image: failed"
Doug Zongkerc9253822014-02-04 12:17:58 -08002122else
2123 log -t recovery "Recovery image already installed"
2124fi
Dan Albert8b72aef2015-03-23 19:13:21 -07002125""" % {'boot_size': boot_img.size,
2126 'boot_sha1': boot_img.sha1,
2127 'recovery_size': recovery_img.size,
2128 'recovery_sha1': recovery_img.sha1,
2129 'boot_type': boot_type,
2130 'boot_device': boot_device,
2131 'recovery_type': recovery_type,
2132 'recovery_device': recovery_device,
2133 'bonus_args': bonus_args}
Doug Zongkerc9253822014-02-04 12:17:58 -08002134
2135 # The install script location moved from /system/etc to /system/bin
Tianjie Xu78de9f12017-06-20 16:52:54 -07002136 # in the L release.
2137 sh_location = "bin/install-recovery.sh"
Tao Bao9f0c8df2015-07-07 18:31:47 -07002138
Tao Bao32fcdab2018-10-12 10:30:39 -07002139 logger.info("putting script in %s", sh_location)
Doug Zongkerc9253822014-02-04 12:17:58 -08002140
2141 output_sink(sh_location, sh)